Skip to content

Run source-typer on crystal standard library, give or take #15682

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

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 13 additions & 13 deletions src/base64.cr
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ module Base64
# Tm93IGlzIHRoZSB0aW1lIGZvciBhbGwgZ29vZCBjb2RlcnMKdG8gbGVhcm4g
# Q3J5c3RhbA==
# ```
def encode(data) : String
def encode(data : String | Slice(UInt8) | StaticArray(UInt8, 5)) : String
slice = data.to_slice
String.new(encode_size(slice.size, new_lines: true)) do |buf|
appender = buf.appender
Expand All @@ -58,7 +58,7 @@ module Base64
# ```
# Base64.encode("Now is the time for all good coders\nto learn Crystal", STDOUT)
# ```
def encode(data, io : IO)
def encode(data : String | Slice(UInt8) | StaticArray(UInt8, 5), io : IO) : Int32
count = 0
encode_with_new_lines(data.to_slice) do |byte|
io << byte.unsafe_chr
Expand Down Expand Up @@ -95,11 +95,11 @@ module Base64
# ```text
# Tm93IGlzIHRoZSB0aW1lIGZvciBhbGwgZ29vZCBjb2RlcnMKdG8gbGVhcm4gQ3J5c3RhbA==
# ```
def strict_encode(data) : String
def strict_encode(data : Slice(UInt8) | StaticArray(UInt8, 5) | String | StaticArray(UInt8, 20) | StaticArray(UInt8, 16)) : String
strict_encode data, CHARS_STD, pad: true
end

private def strict_encode(data, alphabet, pad = false)
private def strict_encode(data : Slice(UInt8) | StaticArray(UInt8, 5) | String | StaticArray(UInt8, 20) | StaticArray(UInt8, 16), alphabet : String, pad : Bool = false) : String
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The static arrays are interesting: they outline that the method should really just take a slice, and there are opportunities for optimizations.

Even the string in the other methods are interesting. There could be a specific overload that would call the slice method.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally, we should be able to express a type restriction for types that have a #to_slice : Bytes method (cf. #934).

Copy link
Member

@straight-shoota straight-shoota Apr 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure about runtime optimizations because private methods are typically inlined anyway, so the static array here likely won't be copied around.
But for sure the compiler would have an easier time if it has to deal with only one method accepting Bytes instead of a multitude of different StaticArray instance types.

slice = data.to_slice
String.new(encode_size(slice.size)) do |buf|
appender = buf.appender
Expand All @@ -115,11 +115,11 @@ module Base64
# ```
# Base64.strict_encode("Now is the time for all good coders\nto learn Crystal", STDOUT)
# ```
def strict_encode(data, io : IO)
def strict_encode(data : Slice(UInt8) | StaticArray(UInt8, 5) | String, io : IO) : Int32
strict_encode_to_io_internal(data, io, CHARS_STD, pad: true)
end

private def strict_encode_to_io_internal(data, io, alphabet, pad)
private def strict_encode_to_io_internal(data : Slice(UInt8) | StaticArray(UInt8, 5) | String, io : String::Builder | IO::Memory, alphabet : String, pad : Bool) : Int32
count = 0
to_base64(data.to_slice, alphabet, pad: pad) do |byte|
count += 1
Expand All @@ -137,7 +137,7 @@ module Base64
#
# The *padding* parameter defaults to `true`. When `false`, enough `=` characters
# are not added to make the output divisible by 4.
def urlsafe_encode(data, padding = true) : String
def urlsafe_encode(data : String | Slice(UInt8), padding : Bool = true) : String
slice = data.to_slice
String.new(encode_size(slice.size)) do |buf|
appender = buf.appender
Expand All @@ -152,13 +152,13 @@ module Base64
# Alphabet" in [RFC 4648](https://tools.ietf.org/html/rfc4648).
#
# The alphabet uses `'-'` instead of `'+'` and `'_'` instead of `'/'`.
def urlsafe_encode(data, io : IO)
def urlsafe_encode(data : String, io : IO) : Int32
strict_encode_to_io_internal(data, io, CHARS_SAFE, pad: true)
end

# Returns the base64-decoded version of *data* as a `Bytes`.
# This will decode either the normal or urlsafe alphabets.
def decode(data) : Bytes
def decode(data : String) : Bytes
slice = data.to_slice
buf = Pointer(UInt8).malloc(decode_size(slice.size))
appender = buf.appender
Expand All @@ -168,7 +168,7 @@ module Base64

# Writes the base64-decoded version of *data* to *io*.
# This will decode either the normal or urlsafe alphabets.
def decode(data, io : IO)
def decode(data : String, io : IO) : Int32
count = 0
from_base64(data.to_slice) do |byte|
io.write_byte byte
Expand All @@ -180,7 +180,7 @@ module Base64

# Returns the base64-decoded version of *data* as a string.
# This will decode either the normal or urlsafe alphabets.
def decode_string(data) : String
def decode_string(data : String) : String
slice = data.to_slice
String.new(decode_size(slice.size)) do |buf|
appender = buf.appender
Expand All @@ -189,13 +189,13 @@ module Base64
end
end

private def encode_size(str_size, new_lines = false)
private def encode_size(str_size : Int32, new_lines : Bool = false) : Int32
size = (str_size * 4 / 3.0).to_i + 4
size += size // LINE_SIZE if new_lines
size
end

private def decode_size(str_size)
private def decode_size(str_size : Int32) : Int32
(str_size * 3 / 4.0).to_i + 4
end

Expand Down
4 changes: 2 additions & 2 deletions src/benchmark/bm.cr
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,13 @@ module Benchmark
end

# Reports a single benchmark unit.
def report(label = " ", &block)
def report(label : String = " ", &block) : Array(Tuple(String, Proc(Nil)))
@label_width = label.size if label.size > @label_width
@reports << {label, block}
end

# :nodoc:
def execute
def execute : Nil
if @label_width > 0
print " " * @label_width
end
Expand Down
14 changes: 7 additions & 7 deletions src/benchmark/ips.cr
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ module Benchmark
end

# Adds code to be benchmarked
def report(label = "", &action) : Benchmark::IPS::Entry
def report(label : String = "", &action) : Benchmark::IPS::Entry
item = Entry.new(label, action)
@items << item
item
Expand Down Expand Up @@ -61,7 +61,7 @@ module Benchmark

# The warmup stage gathers information about the items that is later used
# in the calculation stage
private def run_warmup
private def run_warmup : Nil
@items.each do |item|
GC.collect

Expand All @@ -79,7 +79,7 @@ module Benchmark
end
end

private def run_calculation
private def run_calculation : Nil
@items.each do |item|
GC.collect

Expand Down Expand Up @@ -113,11 +113,11 @@ module Benchmark
end
end

private def ran_items
private def ran_items : Array(Benchmark::IPS::Entry)
@items.select(&.ran?)
end

private def run_comparison
private def run_comparison : Nil
fastest = ran_items.max_by(&.mean)
ran_items.each do |item|
item.slower = (fastest.mean / item.mean).to_f
Expand Down Expand Up @@ -175,12 +175,12 @@ module Benchmark
cycles.times { action.call }
end

def set_cycles(duration, iterations) : Nil
def set_cycles(duration : Time::Span, iterations : Int32) : Nil
@cycles = (iterations / duration.total_milliseconds * 100).to_i
@cycles = 1 if cycles <= 0
end

def calculate_stats(samples) : Nil
def calculate_stats(samples : Array(Float64) | Array(Int32)) : Nil
@ran = true
@size = samples.size
@mean = samples.sum.to_f / size.to_f
Expand Down
22 changes: 11 additions & 11 deletions src/bit_array.cr
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ struct BitArray
arr
end

def ==(other : BitArray)
def ==(other : BitArray) : Bool
return false if size != other.size
# NOTE: If BitArray implements resizing, there may be more than 1 binary
# representation and their hashes for equivalent BitArrays after a downsize as the
Expand Down Expand Up @@ -366,7 +366,7 @@ struct BitArray
end

@[AlwaysInline]
private def set_bits(bytes : Slice(UInt8), value, index, mask)
private def set_bits(bytes : Slice(UInt8), value : Bool, index : Int32, mask : UInt8) : UInt8
if value
bytes[index] |= mask
else
Expand All @@ -376,7 +376,7 @@ struct BitArray

# returns (1 << from) | (1 << (from + 1)) | ... | (1 << to)
@[AlwaysInline]
private def uint8_mask(from, to)
private def uint8_mask(from : Int32, to : Int32) : UInt8
(Int8::MIN >> (to - from)).to_u8! >> (7 - to)
end

Expand All @@ -396,7 +396,7 @@ struct BitArray
# ba.toggle(3)
# ba[3] # => true
# ```
def toggle(index) : Nil
def toggle(index : Int32) : Nil
bit_index, sub_index = bit_index_and_sub_index(index)
@bits[bit_index] ^= 1 << sub_index
end
Expand All @@ -417,7 +417,7 @@ struct BitArray
# ba.toggle(1..-2)
# ba.to_s # => "BitArray[01110]"
# ```
def toggle(range : Range)
def toggle(range : Range) : UInt32?
toggle(*Indexable.range_to_index_and_count(range, size) || raise IndexError.new)
end

Expand All @@ -438,7 +438,7 @@ struct BitArray
# ba.toggle(1, 3)
# ba.to_s # => "BitArray[01110]"
# ```
def toggle(start : Int, count : Int)
def toggle(start : Int, count : Int) : UInt32?
start, count = normalize_start_and_count(start, count)
return if count == 0

Expand All @@ -459,7 +459,7 @@ struct BitArray

# returns (1 << from) | (1 << (from + 1)) | ... | (1 << to)
@[AlwaysInline]
private def uint32_mask(from, to)
private def uint32_mask(from : Int32, to : Int32) : UInt32
(Int32::MIN >> (to - from)).to_u32! >> (31 - to)
end

Expand Down Expand Up @@ -641,7 +641,7 @@ struct BitArray
bit_array
end

private def bit_index_and_sub_index(index)
private def bit_index_and_sub_index(index : Int32) : Tuple(Int32, Int32)
bit_index_and_sub_index(index) { raise IndexError.new }
end

Expand All @@ -652,17 +652,17 @@ struct BitArray
index.divmod(32)
end

protected def clear_unused_bits
protected def clear_unused_bits : UInt32?
# There are no unused bits if `size` is a multiple of 32.
bit_index, sub_index = @size.divmod(32)
@bits[bit_index] &= ~(UInt32::MAX << sub_index) unless sub_index == 0
end

private def bytesize
private def bytesize : Int32
(@size - 1) // 8 + 1
end

private def malloc_size
private def malloc_size : Int32
(@size - 1) // 32 + 1
end
end
10 changes: 5 additions & 5 deletions src/channel.cr
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ class Channel(T)
record UseDefault

class ClosedError < Exception
def initialize(msg = "Channel is closed")
def initialize(msg : String = "Channel is closed")
super(msg)
end
end
Expand Down Expand Up @@ -290,20 +290,20 @@ class Channel(T)
pp.text inspect
end

def self.receive_first(*channels)
def self.receive_first(*channels) : Int32
receive_first channels
end

def self.receive_first(channels : Enumerable(Channel))
def self.receive_first(channels : Enumerable(Channel)) : Int32
_, value = self.select(channels.map(&.receive_select_action))
value
end

def self.send_first(value, *channels) : Nil
def self.send_first(value : Int32, *channels) : Nil
send_first value, channels
end

def self.send_first(value, channels : Enumerable(Channel)) : Nil
def self.send_first(value : Int32, channels : Enumerable(Channel)) : Nil
self.select(channels.map(&.send_select_action(value)))
nil
end
Expand Down
10 changes: 5 additions & 5 deletions src/channel/select.cr
Original file line number Diff line number Diff line change
Expand Up @@ -48,28 +48,28 @@ class Channel(T)
end

# :nodoc:
def self.select(*ops : SelectAction)
def self.select(*ops : SelectAction) : Tuple(Int32, String) | Tuple(Int32, Nil) | Tuple(Int32, Bool | String) | Tuple(Int32, String | Nil) | Tuple(Int32, Bool | String | Nil) | Tuple(Int32, Int32)
self.select ops
end

# :nodoc:
def self.select(ops : Indexable(SelectAction))
def self.select(ops : Indexable(SelectAction)) : Tuple(Int32, Int32) | Tuple(Int32, Nil) | Tuple(Int32, String) | Tuple(Int32, Bool | String) | Tuple(Int32, String | Nil) | Tuple(Int32, Bool | String | Nil) | Tuple(Int32, Bool | Nil) | Tuple(Int32, Int32 | Nil)
i, m = select_impl(ops, false)
raise "BUG: Blocking select returned not ready status" if m.is_a?(NotReady)
return i, m
end

# :nodoc:
def self.non_blocking_select(*ops : SelectAction)
def self.non_blocking_select(*ops : SelectAction) : Tuple(Int32, Channel::NotReady | String) | Tuple(Int32, Bool | Channel::NotReady | String) | Tuple(Int32, Channel::NotReady | String | Nil) | Tuple(Int32, Channel::NotReady | Nil)
self.non_blocking_select ops
end

# :nodoc:
def self.non_blocking_select(ops : Indexable(SelectAction))
def self.non_blocking_select(ops : Indexable(SelectAction)) : Tuple(Int32, Channel::NotReady | String) | Tuple(Int32, Bool | Channel::NotReady | String) | Tuple(Int32, Channel::NotReady | String | Nil) | Tuple(Int32, Channel::NotReady | Nil) | Tuple(Int32, Bool | Channel::NotReady) | Tuple(Int32, Bool | Channel::NotReady | String | Nil) | Tuple(Int32, Channel::NotReady | Exception | Nil)
select_impl(ops, true)
end

private def self.select_impl(ops : Indexable(SelectAction), non_blocking)
private def self.select_impl(ops : Indexable(SelectAction), non_blocking : Bool) : Tuple(Int32, Channel::NotReady | Int32) | Tuple(Int32, Channel::NotReady | Nil) | Tuple(Int32, Channel::NotReady | String) | Tuple(Int32, Bool | Channel::NotReady | String) | Tuple(Int32, Channel::NotReady | String | Nil) | Tuple(Int32, Bool | Channel::NotReady | String | Nil) | Tuple(Int32, Bool | Channel::NotReady) | Tuple(Int32, Bool | Channel::NotReady | Nil) | Tuple(Int32, Channel::NotReady | Int32 | Nil) | Tuple(Int32, Channel::NotReady | Exception | Nil)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Urgh, tuples are nice but they quickly create lots of types. It might be interesting to introduce a type, here 🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seeing the size of the union is a good indication there's confusion on what's expected of this method. Could also be a good opportunity to see why some of the types show up like this as well.

# ops_locks is a duplicate of ops that can be sorted without disturbing the
# index positions of ops
if ops.responds_to?(:unstable_sort_by!)
Expand Down
10 changes: 5 additions & 5 deletions src/char.cr
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ struct Char
end

# :ditto:
def step(*, to limit = nil, exclusive : Bool = false)
def step(*, to limit : Char? = nil, exclusive : Bool = false) : Steppable::StepIterator(Char, Char, Int32)
if limit
direction = limit <=> self
end
Expand Down Expand Up @@ -681,7 +681,7 @@ struct Char
#
# This means characters which are `control?` or `whitespace?` (except for ` `)
# are non-printable.
def printable?
def printable? : Bool
!control? && (!whitespace? || self == ' ')
end

Expand Down Expand Up @@ -746,7 +746,7 @@ struct Char
end

# :ditto:
def dump(io)
def dump(io : String::Builder | IO::Memory) : String::Builder | IO::Memory
io << dump
end

Expand Down Expand Up @@ -1050,11 +1050,11 @@ struct Char
# 'c' === 99 # => true
# 'z' === 99 # => false
# ```
def ===(byte : Int)
def ===(byte : Int) : Bool
ord === byte
end

def clone
def clone : Char
self
end
end
Loading