egonSchiele / contracts.ruby

Contracts for Ruby.
http://egonschiele.github.com/contracts.ruby
BSD 2-Clause "Simplified" License
1.44k stars 82 forks source link

extend Contracts for raised and thrown objects #263

Open md-work opened 7 years ago

md-work commented 7 years ago

Thanks for this really great gem!

It helps a lot checking the types of objects being send up and down the stack trough method calls and returns. Sadly there's still no way to check the type of objects being send trough the stack by the Ruby raise and throw commands.

I'm thinking about something similar to throws Exception in Java. (sure this isn't Java and the checking can only be done dynamically, not statically)

public void example() throws Exception {
    throw new Exception();
}

What do you think about adding such an feature to Contracts?

See also: #193

egonSchiele commented 7 years ago

Hi @md-work, this is a neat idea. Based on the comments in https://github.com/egonSchiele/contracts.ruby/issues/193, would you like to add a contract that says "if an exception is raised, catch it and raise a contract exception"? Or would you want to annotate methods saying "this can throw an exception"?

md-work commented 7 years ago

Hi @egonSchiele I'm talking about "this can throw an exception".

Examples:

Contract String => String raises Errno::ENOENT
def read_file(filename)
    # This raises Errno::ENOENT if the filename doesn't exist.
    # In this case everything's fine the Contract should let the
    # exception pass (or re-raise it without modification).

    # But in case the user doesn't has permissions to read the file,
    # this raises Errno::EACCES.
    # In this case the Contract should catch the exception and raise an
    # own exception instead (e.g. a RaiseContractError).

    return File.read(filename).to_s
end

Contract Integer, Integer => Integer raises Contracts::None
def add(num_a, num_b)
    # This should never raise an exception.
    return num_a + num_b
end

# This method usually never raises an exception. Just in one very rare
# situation, about which the programmer forgot when he wrote the
# contract.
# So if the method gets 23 it raises :illuminati and the Contract
# should catch that and raise an own exception instead.
Contract Integer => Integer raises Contracts::None
def some_fun(number)
    if number = 23
        raise :illuminati
    else
        return number
    end
end

# Same with a fixed contract.
Contract Integer => Integer raises :illuminati
def some_fun(number)
    if number = 23
        raise :illuminati
    else
        return number
    end
end

# Same for throw-catch like for raise-rescue.
Contract Integer => Integer throws :illuminati
def some_fun(number)
    if number = 23
        throw :illuminati
    else
        return number
    end
end

# And for both, raise-rescue and throw-catch.
Contract Integer => Integer throws :illuminati raises :answer
def some_fun(number)
    if number = 23
        raise :illuminati
    elsif number = 42
        throw :answer
    else
        return number
    end
end

Some notes