Why does a SASS exception become `nil` by calling `backtrace` on it? - ruby

The code below defines a hook Kernel#at_exit to capture an exception and do things at exit, and then raises a Sass::SyntaxError by passing an invalid SASS string.
require "sass"
module Kernel
at_exit do
puts "--Before backtrace"
p $!
$!.backtrace
puts "--After backtrace"
p $!
end
end
Sass::Engine.new("Invalid {sass").render
The output it gives is as below:
...
--Before backtrace
#<Sass::SyntaxError: ...>
--After backtrace
nil
...
It indicates that $! was a Sass::SyntaxError, but it became nil right after backtrace has been called on it. Why did $! change just by calling backtrace on it?
This effect does not seem to happen when Sass::SyntaxError is raised manually as follows:
raise Sass::SyntaxError.new("foo")
or when a different type of error is raised (may be wrong).
Edit
I am not sure, but probably sass manipulates the backtrace using set_backtrace when a sass error is raised. This is to provide information about where the sass syntax error was caused in a sass file. And the different behaviour between manually raising an error and programatically raising an error is reminiscent of a bug in Ruby 2.1 that half-way implemented backtrace_locations, but returned nil in some cases. I have a broad guess that these factors are interfering, but am not sure.

The reason it is happening is because this method overrides backtrace method:
def backtrace
return nil if super.nil?
return super if sass_backtrace.all? {|h| h.empty?}
sass_backtrace.map do |h|
"#{h[:filename] || "(sass)"}:#{h[:line]}" +
(h[:mixin] ? ":in `#{h[:mixin]}'" : "")
end + super
end
Where sass_backtrace is an array of hashes populated in initializer. Line which causes $! to be nil is:
return super if sass_backtrace.all? {|h| h.empty?}
This happens only when all? returns nil. I did some fiddling with it, and I found out that the problem always occurs, when we call any iterator which doesn't finish the whole iteration (all? terminates iteration when encounter the first not satisfying element). The problem might be simply reproduced with:
at_exit do
p $! #=> #<RuntimeError: hello>
[<any_non_empty_array>].all? {false}
# Those would break $! as well
# [<ANA>].any? {true}
# [1,2,3].find {|n| n.even?}
# Those will not break $!
# [<ANA>].any? {false}
# [<ANA>].all? {true}
# [1,2,3].find {|n| n > 4}
p $! #=> nil
end
raise 'hello'
The only reason I can think of why it would work like that is that ruby loops are controlled internally with exceptions. When iteration is to be stopped, it is done with special type of exception being raised and rescued outside the loop.
My guess is that Ruby creators didn't want this control exception to be visible in $! variable, since this would suggest that something went wrong, and decided to set it back to nil.

Related

Ruby console - Multiline command with exit

I have a simple ruby script I would like to run in the rails console, using bundle exec rails c
ids = [1, 2]
if ids.length() > 5
puts "More than 5 ids, quitting"
exit
end
ids.each do |id|
puts id
end
The rails console quits the program as soon as it sees the exit command. What would be the best way around this?
One way to do this is to use throw/catch:
ids = [1, 2]
catch(:bail) do
if ids.length > 5
puts "More than 5 ids, quitting"
throw :bail
end
ids.each do |id|
puts id
end
end
catch :bail defines a block that is labeled with the symbol :bail (the name doesn't really matter). It will execute normally until you throw :bail at which point Ruby zips up the call stack until its finds a corresponding catch. If no matching catch is found an UncaughtThrowError will be raised.
This is somewhat like break but can be used in any context and to break out of deeply nested constructs.
Another alternative would be to simply define a method or raise (and rescue) an exception if the input is invalid.
Raise ArgumentError When Passed Arguments are Somehow Invalid
There are a lot of ways to fix your code, but your essential problem is that Kernel#exit will exit both a standard REPL session and an application, not simply return from a method or break out of an if-statement. Since your code doesn't show that you're inside a method, you can't use return because there's no appropriate method or closure to return from. You could possibly work around calling #exit with nested irb sessions or the like, but that's ultimately solving the wrong problem.
In most cases, you want to raise an exception with a useful message rather than simply exit when something goes unfixably wrong. The following would do that in your application, while in an irb or pry console it will raise the exception but remain within your REPL session:
ids = [1, 2]
raise ArgumentError, "More than 5 ids: #{ids}" if ids.length > 5
ids.each { |id| puts id }
If you were inside a method, you could use #warn and then return an appropriate return value instead. However, since you are presumably inside the top-level object of your REPL, there's nothing to return from, so this is likely your best general-purpose option.

Is there a built-in way to check if #next or #peek will raise StopIteration?

I'm working with a few iterators where I have to do something along these lines (enum is an enumerator)
enums_with_zero << enum.rewind if enum.peek == 0
which normally works fine, but this is after there's already been #next called on the enum a few times. The issue with this is that the enum could be at the end and with a few values passed for enum, I have hit the issue where enum.peek raises StopIteration because the enum is completed. Is there a way I could put in a guard to check if enum.peek or enum.next will cause StopIteration before I call it. Something that would have the behavior of this for example?
class Enumerator
def has_next?
begin
peek && true
rescue StopIteration
false
end
end
end
You can rescue the StopIteration explicitly, but there's also the idea that the loop method internally rescues a StopIteration exception by simply exiting the loop. (Inside loop, raise StopIteration has the same effect as break.)
This code simply exits the loop when you try to peek past the end:
a = %w(a b c d e).to_enum
loop do
print a.peek
a.next
end
The code outputs abcde. (It also transparently raises and rescues StopIteration.)
So, if you want to simply ignore the StopIteration exception when you try to peek past the end, just use loop.
Of course, once you peek past the end, you'll get dumped out of the loop. If you don't want that, you can use while and rescue to customize behavior. For example, if you want to avoid exiting if you peek past the end, and exit when you iterate past the end using next, you could do something like this:
a = %w(a b c d e).to_enum
while true
begin
print a.peek
rescue StopIteration
print "\nTried to peek past the end of the enum.\nWe're gonna overlook that.\n"
end
x = a.next rescue $!
break if x.class == StopIteration
end
p 'All done!'
The last two lines in the loop do the same thing as this, which you could use instead:
begin
a.next
rescue StopIteration
break
end
A point to make is that handling StopIteration is Ruby's intended way of dealing with getting to the end of an iterator. Quoting from Matz's book The Ruby Programming Language:
External iterators are quite simple to use: just call next each time you want another
element. When there are no more elements left, next will raise a StopIteration exception.
This may seem unusual—an exception is raised for an expected termination
condition rather than an unexpected and exceptional event. (StopIteration is a descendant
of StandardError and IndexError; note that it is one of the only exception
classes that does not have the word “error” in its name.) Ruby follows Python in this
external iteration technique. By treating loop termination as an exception, it makes
your looping logic extremely simple; there is no need to check the return value of
next for a special end-of-iteration value, and there is no need to call some kind of
next? predicate before calling next.

Catch a parameter error within a function?

Is it possible for a method to handle an error that happens in a parameter?
e.g.
def add(arg1, arg2)
# If the value causes an error, turn it into 0
rescue nil
0
end
arg1 + arg2
end
add(2, 2+nil) => 2
I understand that shielding parameters and eval-ing it later can be a solution, but in my scenario it becomes too cumbersome to do that.
The problem with this is the error occurs on the line where you provide the arguments as arguments are evaluated prior to the method being called.
Every method call is roughly equivalent to this:
arg1 = 2
arg2 = 2 + nil
add(arg1, arg2)
In this case you can see how the argument itself produces an error. The only way to defer the evaluation of an argument is via a block:
add(2) do
2 + nil
end
Changing your definition to:
def add(v)
v + yield
rescue
v + 0
end
Capturing all exceptions blindly is usually a super bad plan, you may have some kind of fundamental mistake in there. It's best to avoid capturing exceptions unless you have expectations as to what kind they will be.
Here's a version of the method with no exception handling:
def add(*args)
args.map(&:to_i).inject(:+)
end
Now nil.to_i returns 0 so you're covered:
add(2,2,nil)
That will evaluate to 2+2+0 internally.
Generally it's a bad plan to try and bury errors like 2+nil. That's a fundamental mistake that should be addressed. If you wanted to handle nil values you would use .to_i to map them down to something usable.
In this case your add method isn't still called and you have to rescue in the caller.

Ruby puts Displaying nil in rescue

I am brand new to Ruby and am coming from a Python background. To help learn the language, I'm porting an existing Python script so I can do a side by side comparison. Thus far, I have a small bit of code and am confused as to why 'nil' prints to the console. Code below:
#!/usr/bin/ruby
require 'date'
backup_dirs = ['/backups/db', '/backups/log']
backup_dirs.each do |backup_dir|
Dir.glob(backup_dir + '/' + '*.*.*.*.*.*').each do |filename|
begin
creation_date = DateTime.strptime(filename.split('.')[3], '%m%d%Y%H%M')
rescue
puts "Skipping #{filename}, invalid file."
end
end
puts 'File is okay.'
end
If the DateTime.strptime method throws an exception, rescue runs and puts prints out the string fine. Except, after it comes a nil. I "googled" and found that puts returns nil. But why does that show only in the rescue area and not in the File is okay. line.
I.e. an example rescue output would be:
Skipping /backups/log/fs.server.dir.999999999999.14.log, invalid file.
nil
And, how do I make it stop displaying that to the console? I'm sure this is a fundamental language thing, just very new to the language so any insight would be much appreciated.
Thanks - Tom
All... sorry. I figured this out. In the actual code I was doing a puts creation_date and that is what was causing the nil to show... as it very well should!
First day learning the language, oops! Did learn a lot about puts though... appreciate all of the answers, sorry for wasting everyones' time.
This is expected behavior. Ruby methods will return the last statement evaluated if return whatever is not specified.
In the sample you have provided you are ending with a puts statement.
puts writes your output then returns nil and subsequently your method returns nil
From irb just run the following two statements to see this in action.
puts 'foo'
'foo'
This is actually because of the #puts method you're calling.
puts(obj, ...) → nil
Writes the given objects to ios as with IO#print. Writes a record separator (typically a
newline) after any that do not already end with a newline sequence. If
called with an array argument, writes each element on a new line. If
called without arguments, outputs a single record separator.
$stdout.puts("this", "is", "a", "test") produces:
this is a test
You can check that in pry/irb:
[9] pry(main)> puts "Hello"
Hello
=> nil

multiple assignment in rescue clause?

I ran into this sample code as an idiom for exception handling in Ruby:
begin
eval string
rescue SyntaxError, NameError => boom
print "String doesn't compile: " + boom
rescue StandardError => bang
print "Error running script: " + bang
end
I'm confused particularly about the local variable assignment line with multiple exceptions:
rescue SyntaxError, NameError => boom. Does that mean the local var boom will take on either the SyntaxError or NameError object? Or is it just the NameError that will be assigned?
It's further confusing because the code itself throws a TypeError, I think perhaps because string is undefined, but that may be beside the point.
I found the code above at http://phrogz.net/programmingruby/tut_exceptions.html. Was that your source?
In any event, the local variable in that code is assigned whichever error is raised; it's just specified after the last one.
And yes, it's throwing TypeError because the errors do not implicitly coerce to a string in today's Ruby. Perhaps they used to when the book was initially published. You need to add .message to the local variable reference to get the error message (e.g. + boom.message).

Resources