I’ve started reading through some of the code in the standard library. I saw that a lot of methods already have type definitions, but I found a few cases that rely on type inference.
I was wondering if the types in the standard library and shards are treated differently to application code, or is there currently no distinction?
When I’m developing and testing an application, I’m only going to be modifying my application source code, and the standard library and any Crystal shards will never change. So would it make sense to perform a first pass on the standard library + shards, where we “lock down” the inferred types for any of these static files? (Or we could say: bake / freeze / crystallize?) And could this even been done as a pre-release step whenever a developer publishes a new Crystal shard? (E.g. Maybe a crystal tool format
option that “expands” all of the inferred types and writes out new crystal files into a build directory?)
As an example, I’m looking at json/from_json.cr:11:
def Object.from_json(string_or_io)
parser = JSON::PullParser.new(string_or_io)
new parser
end
So it makes sense that I get this type error:
$ crystal eval 'require "json"; Array.from_json(123)'
Showing last frame. Use --error-trace for full trace.
In /usr/local/Cellar/crystal/0.31.1/src/json/pull_parser.cr:49:20
49 | @lexer = Lexer.new input
^--
Error: no overload matches 'JSON::Lexer.new' with type Int32
Overloads are:
- JSON::Lexer.new(string : String)
- JSON::Lexer.new(io : IO)
- JSON::Lexer.new()
I can see that the type error is actually coming from the typed Lexer.new input
inside the call to JSON::PullParser.new(string_or_io)
So if I open /usr/local/Cellar/crystal/0.31.1/src/json/pull_parser.cr:49:20
and add the type annotation:
def initialize(input : String | IO)
Then that moves the error further down the chain:
In /usr/local/Cellar/crystal/0.31.1/src/json/from_json.cr:12:29
12 | parser = JSON::PullParser.new(string_or_io)
^--
Error: no overload matches 'JSON::PullParser.new' with type Int32
Overloads are:
- JSON::PullParser.new(input : String | IO)
And then again for /usr/local/Cellar/crystal/0.31.1/src/json/from_json.cr:12:29
:
def Object.from_json(string_or_io : String | IO) : self
parser = JSON::PullParser.new(string_or_io)
new parser
end
$ crystal eval 'require "json"; Array.from_json(123)'
Showing last frame. Use --error-trace for full trace.
error in line 1
Error: no overload matches 'Array(T).from_json' with type Int32
Overloads are:
- Array(T).from_json(string_or_io, &block)
- Object.from_json(string_or_io, root : String)
- Object.from_json(string_or_io : String | IO)
I actually find this error message much more obvious than the original error that talks about @lexer = Lexer.new input
. And this would allow the standard library developers to still use type inference wherever it’s convenient. But all of the inferred types in the standard library could become expanded and “frozen”.
Is Crystal already doing this behind the scenes? If not, what would be some of the downsides?