Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Odd behavior with 'until' vs '&&' #4239

Closed
drosehn opened this issue Apr 4, 2017 · 5 comments
Closed

Odd behavior with 'until' vs '&&' #4239

drosehn opened this issue Apr 4, 2017 · 5 comments

Comments

@drosehn
Copy link

drosehn commented Apr 4, 2017

This is on:

# crystal --version
Crystal 0.21.1 (2017-03-18) LLVM 4.0.0
# uname -a
Darwin my.host 14.5.0 Darwin Kernel Version 14.5.0: Sun Sep 25 22:07:15 PDT 2016; root:xnu-2782.50.9~1/RELEASE_X86_64 x86_64
# # aka macOS 10.10.5 (Yosemite)

I don't understand what is happening in the following. It seems like either until or && is not processed correctly ( and just try to search for issues with &&! ):

  printf "Enter two integer values:\n"

  low_value = nil
  high_value = nil

  until low_value && high_value
    printf "#DBG B: low_value=%s, high_value=%s\n", low_value.inspect, high_value.inspect
    user_ans = STDIN.gets
    if user_ans.nil?
      STDERR.printf "\n <-- EOF! -->\n"
      exit 1
    end

    user_ans = user_ans.strip
    case user_ans
    when /^\d+$/
      if low_value.nil?
        low_value = user_ans.to_i
      else
        high_value = user_ans.to_i
      end
    else
      STDERR.printf "Invalid response:  '%s'\n", user_ans
    end

    printf "#DBG E: low_value=%s, high_value=%s\n", low_value.inspect, high_value.inspect
  end # until

  exit 1  if low_value.nil?
  exit 1  if high_value.nil?

  if low_value > high_value
    tmp = high_value
    high_value = low_value
    low_value = tmp
  end

  printf "%d\n", low_value
  printf "%d\n", high_value

There are two problems I hit with this, and perhaps they are related. First off, if I do not include:

  exit 1  if low_value.nil?
  exit 1  if high_value.nil?

then crystal fails with a compile-time error on if low_value > high_value, complaining that no overload matches 'Int32#>' with type (Int32 | Nil). However, I should not exit the until loop unless both low_value and high_value are not-nil, so why do I need those two checks?

The other issue is that I happened to use until low_value && high_value instead of using while low_value.nil? || high_value.nil?. If I run the above code using the while statement, it works as expected:

Enter two integer values:
#DBG B: low_value=nil, high_value=nil
1
#DBG E: low_value=1, high_value=nil
#DBG B: low_value=1, high_value=nil
2
#DBG E: low_value=1, high_value=2
1
2

But if I run it with the until statement, the result is:

Enter two integer values:
#DBG B: low_value=nil, high_value=nil
1
#DBG E: low_value=1, high_value=nil
#DBG B: low_value=nil, high_value=nil
2
#DBG E: low_value=2, high_value=nil
#DBG B: low_value=nil, high_value=nil
3
#DBG E: low_value=3, high_value=nil
#DBG B: low_value=nil, high_value=nil
4
#DBG E: low_value=4, high_value=nil
#DBG B: low_value=nil, high_value=nil
^C

Why is low_value being reset to nil each time through the until loop? Given the logic inside the loop, I can change that until to be until high_value, and then it behaves as expected. Note that if I did use that condition, then I would understand why I need the check for low_value.nil? outside of the loop.

@drosehn
Copy link
Author

drosehn commented Apr 4, 2017

On gitter, @zatherz noticed that the same issue comes up if you use while !(low_value && high_value).

@asterite
Copy link
Member

asterite commented Apr 4, 2017

Reduced:

a = nil
until a
  a = 1
end
p typeof(a) # => (Int32 | Nil)

Basically until (or while) don't do type filtering after them. This is probably a good enhancement.

@drosehn
Copy link
Author

drosehn commented Apr 4, 2017

Well, that's the first issue. But the second one seems more significant to me!

@zatherz
Copy link
Contributor

zatherz commented Apr 4, 2017

Reduced second issue with no infinite loop:
Using while !(a && b): https://carc.in/#/r/1t8j (unexpected behavior)
Using while !a || !b: https://carc.in/#/r/1t8n (expected behavior)
Using while !(Glob.a && b) (Glob is a class): https://carc.in/#/r/1t8p (expected behavior)
Using while !Glob.a || !b (same result): https://carc.in/#/r/1t8q (expected behavior)
Using while !(a && b), assigning a to Glob.a: https://carc.in/#/r/1t8r (unexpected behavior with a, expected behavior with Glob.a)
Using while !(a && b), assigning a to Glob.a: https://carc.in/#/r/1t8s (expected behavior with both a and Glob.a)

To sum it up, it seems like setting the value of a in the scope of a while !(a && b) actually creates a new local variable instead of modifying the one in the outer scope, while while !a || !b has the expected result of modifying the local variable in the outer scope.

EDIT:
First test, but printing the value of a after the loop: https://carc.in/#/r/1t8u
Actually, it does change the variable of the outer scope, but the value of a at the beginning of the scope of each loop is set back to the initial value before the loop?

EDIT 2:
Expanding the while condition of the first test from !(a && b) to !((a != nil) && b) produces expected behavior: https://carc.in/#/r/1t90
But, expanding the condition from !(a && b) to !(!a.nil? && b) retains the unexpected effect: https://carc.in/#/r/1t92

@asterite
Copy link
Member

asterite commented Apr 5, 2017

@drosehn Sorry, I didn't see the second issue the first time I read it. I stopped at the first issue. It's probably better to report issues separately, and try to reduce them as much as possible so the bug is super clear.

I split this issue in two which mention the bugs in a reduced way: #4242 and #4243

@asterite asterite closed this as completed Apr 5, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants