Sorbet not picking up obvious mismatch of Hash signature - ruby

Given the following code:
# typed: strict
extend T::Sig
sig { params(x: T::Hash[String,String]).returns(NilClass) }
def foo(x); end
foo("foo" => 123)
Playground Link
I would expect Sorbet to recognize that the passed hash has the wrong value parameter. How can I properly declare a signature which will ensure the hash passed only contains allowed values types?

Unfortunately, this is a known issue in Sorbet
https://github.com/sorbet/sorbet/issues/713
You're doing everything right, and Sorbet let you down. Feel free to follow the issue, but we estimate that this particular bug will be quite hard to fix, because of some other constraints we're trying to maintain.

Related

Require Elem when using Sorbet RBI

I want to extend the standard Array with a new instance method but I keep getting a runtime error about the type_member not being found.
The definition looks like this.
class Array
extend T::Sig
extend T::Generic
sig do
type_parameters(:U)
.params(split_elem: T.type_parameter(:U))
.returns(T::Array[Elem])
end
def split_on(split_elem)
output = []
group = []
each do |elem|
if elem.eql?(split_elem)
output << group unless group.empty?
group = []
else
group << elem
end
end
output << group unless group.empty?
output
end
end
Is there a way to explicitly require the rbi file declaring Elem?
When trying to run the code, I get the following error. I have tried requiring the sorbet-runtime but no success so far.
NameError: uninitialized constant Array::Elem
.returns(T::Array[Elem])
RBI files are purely static artifacts, they are not meant to be required or run in any way. So, the high-level answer to your question is "no, you can't require RBI files".
The problem you are facing is that you are adding a signature that is statically checkable (i.e. Sorbet can understand Elem and type-check your code without running it), but it is not valid at runtime, since there is no actual Elem constant under the Ruby Array class.
There are three ways you can square this circle:
You want to actually return the same type of the receiver, in which case, you can just use T.self_type as the return type, which will solve your problem with Elem. Docs for T.self_type are here.
You can move the inline signature for Array#split_on to an RBI file, which will make the signature checked only statically (based on what I said about RBI files above), or
You can use T::Sig::WithoutRuntime.sig instead of sig to write your signature, as explained here.

How to define a signature for a hash with attributes in Sorbet?

(Note that this isn't reproducible on sorbet.run, it's only reproducible with a local copy of Sorbet as far as I can tell)
I was hoping I could use the Typed Structs feature to create a method signature where one of the parameters is an options hash, but this doesn't work:
# typed: true
require 'sorbet-runtime'
extend T::Sig
class OptionsStruct < T::Struct
prop :x, Integer, default: 1
end
sig { params(options: OptionsStruct).void }
def method(options)
puts options.x
end
# This works
method(OptionsStruct.new({x: 2}))
# This causes the typechecker to throw.
method({x: 2})
Essentially, when you typecheck this file it complains about passing a hash in, when a Struct is expected. My question is: how can I define a valid signature for a hash that has specific parameters? Structs clearly aren't working here. While I haven't tried Shapes, according to the docs they're very limited, so I'd prefer not to use them if possible.
The documentation on generics mentions Hashes but seems to suggest they can only be used if the hash's keys and values are all of the same types (e.g. Hash<Symbol, String> requires that all keys be Symbols and all values be Strings), and doesn't provide any way (as far as I know) to define a hash with specific keys.
Thanks!
Essentially, you have to choose to go one of various ways, (three which you've already mentioned):
Use a T::Hash[KeyType, ValueType]. This allows you to use the {} syntax when calling a method that takes it as a param, but forces you to use the same type of key and value for every entry.
Use a T::Hash[KeyType, Object]. This is a bit more flexible on the type of the value... but you loose type information.
Use a T::Hash[KeyType, T.any(Type1, Type2, ...). This is a middle ground between 1 and 2.
Use shapes. As the docs say, the functionality might change and are experimental. It's the nicest way to model something like this without imposing the use of the T::Struct to the caller:
sig { params(options: {x: Integer}).void }
def method(options)
puts options[:x]
end
Use a T::Struct, like you did. This forces you to call the method with MyStruct.new(prop1: x, prop2: y, ...)
All of them are valid, with 4 and 5 being the ones that give you the most type safety. Of the two, 4 is the most flexible on the caller, but 5 is the one that you know Sorbet is not going to change support in the short/medium term.

Ruby's 'is_a?' is returning false for built-in types, what's happening?

I've only been learning the deep parts of Ruby for a few months so apologies if this is a bit of a dumb question. I'm attempting to recursively iterate through an Array that may contain nested Arrays and as such I need to check the type of the current element. I have the following code as a small test:
arr = [ 1..2, [3..4, [5..6]], [7..8, [9..10]] ]
arr.each do |node|
p node.class
p node.instance_of? Array
end
When I run it, I get the following output:
Range
false
Array
false
Array
false
I expected the last two to return True, given I have an Array containing a Range and two nested Arrays.
What's even weirder, is if I write the following:
node.class.name == "Array"
It returns True, as it should.
What's happening here?
Ruby Version: MRI 1.9.3-p194
Note: I eventually realised that this was occurring due to the way I namespace my code using modules to avoid code-collision, like so, but also verify object identity in a naughty way:
module MyProg
class MyClass
attr_reader :my_array
def initialize(size)
#my_array = Array.new(size)
end
end
end
MyProg::MyClass.new
Doing this isolates your code but has the downfall of causing all class lookups to be resolved starting from under your namespace. This means that in the above example, my_array.class would actually resolve to MyProg::Array instead of the global Array class.
If you namespace like this and you still want to use this method, you can remedy it by using the double-colon global identifier before the class to force Ruby to begin lookup from the global namespace:
arr.is_a? ::Array
arr.is_a? ::String
Given Ruby's Duck Typing abilities however (and for better code maintenance later on), you should really be testing the behaviour of the object as-per Peter's suggestion below. As such I'm marking his answer as correct for some excellent help given to a learner!
I wrote another answer, but one major question is - why are you doing this? Why not, instead, just call flatten on the array so you just get the entries? Or, check for the behavior of the objects instead. You might need to give more detail about what you require.
You really mean is_a?, which is a more general test to see if the node is of type Array, rather than a specific instance of the specific Array class that you mention. See here for more details. But if you just use is_a? everything will make sense.
I ran your code and got these results.
Range
false
Array
true
Array
true
I'm running ruby 1.9.3p125

ruby explicit method return types

I'm learning ruby (have a java/groovy background) and it looks like when writing methods, the method signature is
def method_name
# do something
end
With ruby's dynamic nature, I understand why the return type is not explicitly declared. But let's say I have a method called get_name. As a caller of the code, I may not be sure if it returns a String or some custom Name type. Is there a way to explicitly declare the return type? And (if it is possible) is this really ever done in ruby to make it clear what the return value is?
Update 2019: still no static typing or type annotations built into ruby, but we now have 3rd party type checkers. See this answer for more details.
Java performs static type checks. It ensures at compile time that all type requirements are met. Ruby is more dynamic, it doesn't do compile time checks and its type system is often referred to as "duck typing". That is, "If it walks like a duck and quacks like a duck, then it must be a duck".
If a method expects an object that responds to certain messages (has certain methods), then it doesn't care about actual type of the object as long it conforms to the protocol. If this is backed up by good test suite, then it's (arguably) a better way, because it allows for faster prototyping and reduced visual clutter. Less visual clutter - code is more expressive and understandable. Benefits of that are obvious. :)
In 2019 Stripe released their type-checker for ruby, Sorbet. It is now possible to specify method signatures. Can look like this:
sig {returns(String)}
def name
"Sergio"
end
If you, for example, make a mistake with types, you'll get interesting errors.
Code:
sig {returns(Integer)}
def name
"Sergio"
end
Errors:
editor.rb:12: Returning value that does not conform to method result type https://srb.help/7005
12 | "Sergio"
^^^^^^^^
Expected Integer
editor.rb:11: Method name has return type Integer
11 | def name
^^^^^^^^
Got String("Sergio") originating from:
editor.rb:12:
12 | "Sergio"
^^^^^^^^
Errors: 1
You can play with the checker in the online playground.
The short answer is this: It is imposible and will never become posible. And it will never be used.
Here's the long answer: Ruby doesn't operate on types. Ruby operates on the generic object. Everything, at some point, is just a BasicObject. Ruby uses Duck Typeing, which has the core principle
If it walks like a duck and talks like a duck, the interpreter is happy to treat it as a duck.
You can check class with Object#class (returns class of an object) and Object#is_a?(klass), but it is strongly discouraged by the community. Just think of it logically, read source code, and use documentation.
I find that the comment annotations provided by RubyMine / IntelliJ to be very helpful for this. For example:
# #param [String] time_zone_offset_string eg. +10:00
# #return [Array] An array containing sign, hours and minutes
def self.parse_timezone_string(time_zone_offset_string)
... parse the timezone string ...
return [sign, hours, minutes]
end
These annotations don't seem to be feature of RDoc - it would be great if they were.
As far as I know, it is impossible for ruby to define type of the return value of a method. But you can use method such as is_a? or class to check type.

Array of Types in Ruby

I am trying to create instances of objects of various types by iterating and checking for validity. I need an array of types so I can do something like this:
def tryClasses(in)
types = [Foo::A, Foo::B, Foo::C]
types.each do |type|
a = type.new(in)
return a != null
end
end
How do I create and array of class types?
Doing it this way I am getting a NoMethodError (undefined method 'A' for Foo)
Apart from the obvious syntactic errors (e.g. in is a reseved word, and null is spelled nil in Ruby), the code you showed should work just fine as it is, and indeed it does when I copy&paste it into my Ruby installation. This assumes, of course, that the classes Foo::A, Foo::B and Foo::C actually exist. If they don't, then the code obviously cannot possibly work.
It is, however, completely un-Rubyish and violates just about every coding convention in the book:
indentation is 2 spaces
method names are snake_case, not camelCase
explicitly checking for equality to nil is a no-no, simply calling #nil? is much preferred
try_classes isn't exactly an intention-revealing method name
and WTF does in mean?
Rubyists much prefer higher-order methods over explicit looping
Here's a more Rubyish version of the code you wrote:
def can_create_object?(*args)
[Foo::A, Foo::B, Foo::C].none? do |klass|
klass.new(*args).nil?
end
end
However, note that I am pretty convinced that the whole idea is fundamentally flawed.

Resources