+ XML
+
+ namespace = doc.root.not_nil!.namespace.should be_a XML::Namespace
+ namespace.href.should eq "http://a9.com/-/spec/opensearchrss/1.0/"
+ namespace.prefix.should eq "openSearch"
+ end
+ end
+
+ describe "with a default prefix" do
+ it "return the default namespace" do
+ doc = XML.parse(<<-XML)
+
+
+ XML
+
+ namespace = doc.root.not_nil!.namespace.should be_a XML::Namespace
+ namespace.href.should eq "http://a9.com/-/spec/opensearchrss/1.0/"
+ namespace.prefix.should be_nil
+ end
+ end
+
+ describe "without an explicit declaration on the node" do
+ it "returns the related namespace" do
+ doc = XML.parse(<<-XML)
+
+
+
+
+
+ XML
+
+ root = doc.root.not_nil!
+
+ namespace = root.children[1].namespace.should be_a XML::Namespace
+ namespace.href.should eq "http://www.w3.org/2005/Atom"
+ namespace.prefix.should be_nil
+
+ namespace = root.children[3].namespace.should be_a XML::Namespace
+ namespace.href.should eq "https://a-namespace"
+ namespace.prefix.should eq "a"
+ end
+ end
+ end
+
+ describe "when the node does not have namespace" do
+ it "should return nil" do
+ doc = XML.parse(<<-XML)
+
+
+ XML
+
+ doc.root.not_nil!.namespace.should be_nil
+ end
+ end
- namespaces.size.should eq(2)
- namespaces[0].href.should eq("http://www.w3.org/2005/Atom")
- namespaces[0].prefix.should be_nil
- namespaces[1].href.should eq("http://a9.com/-/spec/opensearchrss/1.0/")
- namespaces[1].prefix.should eq("openSearch")
+ describe "when the element does not have a namespace, but has namespace declarations" do
+ it "should return nil" do
+ doc = XML.parse(<<-XML)
+
+
+ XML
+
+ doc.root.not_nil!.namespace.should be_nil
+ end
+ end
end
- it "returns empty array if no namespaces scopes exists" do
- doc = XML.parse(<<-XML
-
- John
- XML
- )
- namespaces = doc.root.not_nil!.namespace_scopes
+ describe "#namespace_definitions" do
+ it "returns namespaces explicitly defined" do
+ doc = XML.parse(<<-XML)
+
+
+
+
+ XML
- namespaces.size.should eq(0)
+ namespaces = doc.root.not_nil!.first_element_child.not_nil!.namespace_definitions
+
+ namespaces.size.should eq(1)
+ namespaces[0].href.should eq("http://c")
+ namespaces[0].prefix.should eq "c"
+ end
+
+ it "returns an empty array if no namespaces are defined" do
+ doc = XML.parse(<<-XML)
+
+
+
+
+ XML
+
+ doc.root.not_nil!.first_element_child.not_nil!.namespace_definitions.should be_empty
+ end
end
- it "gets root namespaces as hash" do
- doc = XML.parse(<<-XML
-
-
-
- XML
- )
- namespaces = doc.root.not_nil!.namespaces
- namespaces.should eq({
- "xmlns" => "http://www.w3.org/2005/Atom",
- "xmlns:openSearch": "http://a9.com/-/spec/opensearchrss/1.0/",
- })
+ describe "#namespace_scopes" do
+ it "gets root namespaces scopes" do
+ doc = XML.parse(<<-XML)
+
+
+
+ XML
+
+ namespaces = doc.root.not_nil!.namespace_scopes
+
+ namespaces.size.should eq(2)
+ namespaces[0].href.should eq("http://www.w3.org/2005/Atom")
+ namespaces[0].prefix.should be_nil
+ namespaces[1].href.should eq("http://a9.com/-/spec/opensearchrss/1.0/")
+ namespaces[1].prefix.should eq("openSearch")
+ end
+
+ it "returns empty array if no namespaces scopes exists" do
+ doc = XML.parse(<<-XML)
+
+ John
+ XML
+
+ namespaces = doc.root.not_nil!.namespace_scopes
+
+ namespaces.size.should eq(0)
+ end
+
+ it "includes parent namespaces" do
+ doc = XML.parse(<<-XML)
+
+
+
+
+ XML
+
+ namespaces = doc.root.not_nil!.first_element_child.not_nil!.namespace_scopes
+
+ namespaces.size.should eq(3)
+ namespaces[0].href.should eq("http://c")
+ namespaces[0].prefix.should eq "c"
+ namespaces[1].href.should eq("http://www.w3.org/2005/Atom")
+ namespaces[1].prefix.should be_nil
+ namespaces[2].href.should eq("http://a9.com/-/spec/opensearchrss/1.0/")
+ namespaces[2].prefix.should eq("openSearch")
+ end
+ end
+
+ describe "#namespaces" do
+ it "gets root namespaces as hash" do
+ doc = XML.parse(<<-XML)
+
+
+
+ XML
+
+ namespaces = doc.root.not_nil!.namespaces
+ namespaces.should eq({
+ "xmlns" => "http://www.w3.org/2005/Atom",
+ "xmlns:openSearch" => "http://a9.com/-/spec/opensearchrss/1.0/",
+ })
+ end
+
+ it "includes parent namespaces" do
+ doc = XML.parse(<<-XML)
+
+
+
+
+ XML
+
+ namespaces = doc.root.not_nil!.first_element_child.not_nil!.namespaces
+ namespaces.should eq({
+ "xmlns:c" => "http://c",
+ "xmlns" => "http://www.w3.org/2005/Atom",
+ "xmlns:openSearch" => "http://a9.com/-/spec/opensearchrss/1.0/",
+ })
+ end
+
+ it "returns an empty hash if there are no namespaces" do
+ doc = XML.parse(<<-XML)
+
+
+
+
+ XML
+
+ namespaces = doc.root.not_nil!.first_element_child.not_nil!.namespaces
+ namespaces.should eq({} of String => String?)
+ end
end
it "reads big xml file (#1455)" do
@@ -217,11 +370,11 @@ describe XML do
end
it "sets node text/content" do
- doc = XML.parse(<<-XML
+ doc = XML.parse(<<-XML)
John
XML
- )
+
root = doc.root.not_nil!
root.text = "Peter"
root.text.should eq("Peter")
@@ -231,11 +384,11 @@ describe XML do
end
it "doesn't set invalid node content" do
- doc = XML.parse(<<-XML
+ doc = XML.parse(<<-XML)
John
XML
- )
+
root = doc.root.not_nil!
expect_raises(Exception, "Cannot escape") do
root.content = "\0"
diff --git a/src/array.cr b/src/array.cr
index 066077c5ecff..30b9eb51057d 100644
--- a/src/array.cr
+++ b/src/array.cr
@@ -883,9 +883,8 @@ class Array(T)
# a = [1, 2, 3, 4]
# a.fill { |i| i * i } # => [0, 1, 4, 9]
# ```
- def fill
- each_index { |i| @buffer[i] = yield i }
-
+ def fill(& : Int32 -> T) : self
+ to_unsafe_slice.fill { |i| yield i }
self
end
@@ -900,12 +899,12 @@ class Array(T)
# a = [1, 2, 3, 4]
# a.fill(2) { |i| i * i } # => [1, 2, 4, 9]
# ```
- def fill(from : Int)
+ def fill(from : Int, & : Int32 -> T) : self
from += size if from < 0
raise IndexError.new unless 0 <= from < size
- from.upto(size - 1) { |i| @buffer[i] = yield i }
+ to_unsafe_slice(from, size - from).fill(offset: from) { |i| yield i }
self
end
@@ -923,14 +922,14 @@ class Array(T)
# a = [1, 2, 3, 4, 5, 6]
# a.fill(2, 2) { |i| i * i } # => [1, 2, 4, 9, 5, 6]
# ```
- def fill(from : Int, count : Int)
+ def fill(from : Int, count : Int, & : Int32 -> T) : self
return self if count <= 0
from += size if from < 0
raise IndexError.new unless 0 <= from < size && from + count <= size
- from.upto(from + count - 1) { |i| @buffer[i] = yield i }
+ to_unsafe_slice(from, count).fill(offset: from) { |i| yield i }
self
end
@@ -942,7 +941,7 @@ class Array(T)
# a = [1, 2, 3, 4, 5, 6]
# a.fill(2..3) { |i| i * i } # => [1, 2, 4, 9, 5, 6]
# ```
- def fill(range : Range)
+ def fill(range : Range, & : Int32 -> T) : self
fill(*Indexable.range_to_index_and_count(range, size) || raise IndexError.new) do |i|
yield i
end
@@ -954,15 +953,9 @@ class Array(T)
# a = [1, 2, 3]
# a.fill(9) # => [9, 9, 9]
# ```
- def fill(value : T)
- {% if Number::Primitive.union_types.includes?(T) %}
- if value == 0
- to_unsafe.clear(size)
- return self
- end
- {% end %}
-
- fill { value }
+ def fill(value : T) : self
+ to_unsafe_slice.fill(value)
+ self
end
# Replaces every element in `self`, starting at *from*, with the given *value*. Returns `self`.
@@ -973,22 +966,14 @@ class Array(T)
# a = [1, 2, 3, 4, 5]
# a.fill(9, 2) # => [1, 2, 9, 9, 9]
# ```
- def fill(value : T, from : Int)
- {% if Number::Primitive.union_types.includes?(T) %}
- if value == 0
- from += size if from < 0
+ def fill(value : T, from : Int) : self
+ from += size if from < 0
- raise IndexError.new unless 0 <= from < size
+ raise IndexError.new unless 0 <= from < size
- (to_unsafe + from).clear(size - from)
+ to_unsafe_slice(from, size - from).fill(value)
- self
- else
- fill(from) { value }
- end
- {% else %}
- fill(from) { value }
- {% end %}
+ self
end
# Replaces every element in `self`, starting at *from* and only *count* times,
@@ -1000,24 +985,16 @@ class Array(T)
# a = [1, 2, 3, 4, 5]
# a.fill(9, 2, 2) # => [1, 2, 9, 9, 5]
# ```
- def fill(value : T, from : Int, count : Int)
- {% if Number::Primitive.union_types.includes?(T) %}
- if value == 0
- return self if count <= 0
+ def fill(value : T, from : Int, count : Int) : self
+ return self if count <= 0
- from += size if from < 0
+ from += size if from < 0
- raise IndexError.new unless 0 <= from < size && from + count <= size
+ raise IndexError.new unless 0 <= from < size && from + count <= size
- (to_unsafe + from).clear(count)
+ to_unsafe_slice(from, count).fill(value)
- self
- else
- fill(from, count) { value }
- end
- {% else %}
- fill(from, count) { value }
- {% end %}
+ self
end
# Replaces every element in *range* with *value*. Returns `self`.
@@ -1028,18 +1005,8 @@ class Array(T)
# a = [1, 2, 3, 4, 5]
# a.fill(9, 2..3) # => [1, 2, 9, 9, 5]
# ```
- def fill(value : T, range : Range)
- {% if Number::Primitive.union_types.includes?(T) %}
- if value == 0
- fill(value, *Indexable.range_to_index_and_count(range, size) || raise IndexError.new)
-
- self
- else
- fill(range) { value }
- end
- {% else %}
- fill(range) { value }
- {% end %}
+ def fill(value : T, range : Range) : self
+ fill(value, *Indexable.range_to_index_and_count(range, size) || raise IndexError.new)
end
# Returns the first *n* elements of the array.
@@ -1279,7 +1246,7 @@ class Array(T)
end
def self.product(arrays : Array(Array))
- result = [] of Array(typeof(arrays.first.first))
+ result = [] of Array(typeof(Enumerable.element_type Enumerable.element_type arrays))
each_product(arrays) do |product|
result << product
end
@@ -1503,7 +1470,7 @@ class Array(T)
# Reverses in-place all the elements of `self`.
def reverse!
- Slice.new(@buffer, size).reverse!
+ to_unsafe_slice.reverse!
self
end
@@ -1704,8 +1671,16 @@ class Array(T)
# a.sort # => [1, 2, 3]
# a # => [3, 1, 2]
# ```
- def sort(*, stable : Bool = true) : Array(T)
- dup.sort!(stable: stable)
+ def sort : Array(T)
+ dup.sort!
+ end
+
+ # :ditto:
+ #
+ # This method does not guarantee stability between equally sorting elements.
+ # Which results in a performance advantage over stable sort.
+ def unstable_sort : Array(T)
+ dup.unstable_sort!
end
# Returns a new array with all elements sorted based on the comparator in the
@@ -1722,12 +1697,24 @@ class Array(T)
# b # => [3, 2, 1]
# a # => [3, 1, 2]
# ```
- def sort(*, stable : Bool = true, &block : T, T -> U) : Array(T) forall U
+ def sort(&block : T, T -> U) : Array(T) forall U
+ {% unless U <= Int32? %}
+ {% raise "expected block to return Int32 or Nil, not #{U}" %}
+ {% end %}
+
+ dup.sort! &block
+ end
+
+ # :ditto:
+ #
+ # This method does not guarantee stability between equally sorting elements.
+ # Which results in a performance advantage over stable sort.
+ def unstable_sort(&block : T, T -> U) : Array(T) forall U
{% unless U <= Int32? %}
{% raise "expected block to return Int32 or Nil, not #{U}" %}
{% end %}
- dup.sort!(stable: stable, &block)
+ dup.unstable_sort!(&block)
end
# Modifies `self` by sorting all elements based on the return value of their
@@ -1738,8 +1725,17 @@ class Array(T)
# a.sort!
# a # => [1, 2, 3]
# ```
- def sort!(*, stable : Bool = true) : Array(T)
- Slice.new(to_unsafe, size).sort!(stable: stable)
+ def sort! : Array(T)
+ to_unsafe_slice.sort!
+ self
+ end
+
+ # :ditto:
+ #
+ # This method does not guarantee stability between equally sorting elements.
+ # Which results in a performance advantage over stable sort.
+ def unstable_sort! : Array(T)
+ to_unsafe_slice.unstable_sort!
self
end
@@ -1756,12 +1752,25 @@ class Array(T)
# a.sort! { |a, b| b <=> a }
# a # => [3, 2, 1]
# ```
- def sort!(*, stable : Bool = true, &block : T, T -> U) : Array(T) forall U
+ def sort!(&block : T, T -> U) : Array(T) forall U
+ {% unless U <= Int32? %}
+ {% raise "expected block to return Int32 or Nil, not #{U}" %}
+ {% end %}
+
+ to_unsafe_slice.sort!(&block)
+ self
+ end
+
+ # :ditto:
+ #
+ # This method does not guarantee stability between equally sorting elements.
+ # Which results in a performance advantage over stable sort.
+ def unstable_sort!(&block : T, T -> U) : Array(T) forall U
{% unless U <= Int32? %}
{% raise "expected block to return Int32 or Nil, not #{U}" %}
{% end %}
- Slice.new(to_unsafe, size).sort!(stable: stable, &block)
+ to_unsafe_slice.unstable_sort!(&block)
self
end
@@ -1775,8 +1784,16 @@ class Array(T)
# b # => ["fig", "pear", "apple"]
# a # => ["apple", "pear", "fig"]
# ```
- def sort_by(*, stable : Bool = true, &block : T -> _) : Array(T)
- dup.sort_by!(stable: stable) { |e| yield(e) }
+ def sort_by(&block : T -> _) : Array(T)
+ dup.sort_by! { |e| yield(e) }
+ end
+
+ # :ditto:
+ #
+ # This method does not guarantee stability between equally sorting elements.
+ # Which results in a performance advantage over stable sort.
+ def unstable_sort_by(&block : T -> _) : Array(T)
+ dup.unstable_sort_by! { |e| yield(e) }
end
# Modifies `self` by sorting all elements. The given block is called for
@@ -1788,8 +1805,20 @@ class Array(T)
# a.sort_by! { |word| word.size }
# a # => ["fig", "pear", "apple"]
# ```
- def sort_by!(*, stable : Bool = true, &block : T -> _) : Array(T)
- sorted = map { |e| {e, yield(e)} }.sort!(stable: stable) { |x, y| x[1] <=> y[1] }
+ def sort_by!(&block : T -> _) : Array(T)
+ sorted = map { |e| {e, yield(e)} }.sort! { |x, y| x[1] <=> y[1] }
+ @size.times do |i|
+ @buffer[i] = sorted.to_unsafe[i][0]
+ end
+ self
+ end
+
+ # :ditto:
+ #
+ # This method does not guarantee stability between equally sorting elements.
+ # Which results in a performance advantage over stable sort.
+ def unstable_sort_by!(&block : T -> _) : Array(T)
+ sorted = map { |e| {e, yield(e)} }.unstable_sort! { |x, y| x[1] <=> y[1] }
@size.times do |i|
@buffer[i] = sorted.to_unsafe[i][0]
end
@@ -1857,7 +1886,7 @@ class Array(T)
# a # => [[:a, :b], [:c, :d], [:e, :f]]
# ```
def transpose
- return Array(Array(typeof(first.first))).new if empty?
+ return Array(Array(typeof(Enumerable.element_type Enumerable.element_type self))).new if empty?
len = self[0].size
(1...@size).each do |i|
@@ -1865,8 +1894,8 @@ class Array(T)
raise IndexError.new if len != l
end
- Array(Array(typeof(first.first))).new(len) do |i|
- Array(typeof(first.first)).new(@size) do |j|
+ Array(Array(typeof(Enumerable.element_type Enumerable.element_type self))).new(len) do |i|
+ Array(typeof(Enumerable.element_type Enumerable.element_type self)).new(@size) do |j|
self[j][i]
end
end
@@ -2217,6 +2246,15 @@ class Array(T)
@offset_to_buffer = 0
end
+ private def to_unsafe_slice
+ Slice.new(@buffer, size)
+ end
+
+ private def to_unsafe_slice(index : Int, count : Int)
+ index, count = normalize_start_and_count(index, count)
+ Slice.new(@buffer + index, count)
+ end
+
protected def to_lookup_hash
to_lookup_hash { |elem| elem }
end
@@ -2235,7 +2273,7 @@ class Array(T)
# Optimize for the case of looking for a byte in a byte slice
if T.is_a?(UInt8.class) &&
(object.is_a?(UInt8) || (object.is_a?(Int) && 0 <= object < 256))
- return Slice.new(to_unsafe, size).fast_index(object, offset)
+ return to_unsafe_slice.fast_index(object, offset)
end
super
diff --git a/src/benchmark/ips.cr b/src/benchmark/ips.cr
index 197eb0968366..47229a1536a1 100644
--- a/src/benchmark/ips.cr
+++ b/src/benchmark/ips.cr
@@ -172,12 +172,12 @@ module Benchmark
cycles.times { action.call }
end
- def set_cycles(duration, iterations)
+ def set_cycles(duration, iterations) : Nil
@cycles = (iterations / duration.total_milliseconds * 100).to_i
@cycles = 1 if cycles <= 0
end
- def calculate_stats(samples)
+ def calculate_stats(samples) : Nil
@ran = true
@size = samples.size
@mean = samples.sum.to_f / size.to_f
diff --git a/src/big/big_int.cr b/src/big/big_int.cr
index c5625d6a1042..295c42263258 100644
--- a/src/big/big_int.cr
+++ b/src/big/big_int.cr
@@ -410,36 +410,129 @@ struct BigInt < Int
# TODO: check hash equality for numbers >= 2**63
def_hash to_i64!
- # Returns a string representation of self.
- #
- # ```
- # require "big"
- #
- # BigInt.new("123456789101101987654321").to_s # => 123456789101101987654321
- # ```
- def to_s : String
- String.new(to_cstr)
+ def to_s(base : Int = 10, *, precision : Int = 1, upcase : Bool = false) : String
+ raise ArgumentError.new("Invalid base #{base}") unless 2 <= base <= 36 || base == 62
+ raise ArgumentError.new("upcase must be false for base 62") if upcase && base == 62
+ raise ArgumentError.new("Precision must be non-negative") unless precision >= 0
+
+ case {self, precision}
+ when {0, 0}
+ ""
+ when {0, 1}
+ "0"
+ when {1, 1}
+ "1"
+ else
+ count = LibGMP.sizeinbase(self, base).to_i
+ negative = self < 0
+
+ if precision <= count
+ len = count + (negative ? 1 : 0)
+ String.new(len + 1) do |buffer| # null terminator required by GMP
+ buffer[len - 1] = 0
+ LibGMP.get_str(buffer, upcase ? -base : base, self)
+
+ # `sizeinbase` may be 1 greater than the exact value
+ if buffer[len - 1] == 0
+ if precision == count
+ # In this case the exact `count` is `precision - 1`, i.e. one zero
+ # should be inserted at the beginning of the number
+ # e.g. precision = 3, count = 3, exact count = 2
+ # "85\0\0" -> "085\0" for positive
+ # "-85\0\0" -> "-085\0" for negative
+ start = buffer + (negative ? 1 : 0)
+ start.move_to(start + 1, count - 1)
+ start.value = '0'.ord.to_u8
+ else
+ len -= 1
+ end
+ end
+
+ base62_swapcase(Slice.new(buffer, len)) if base == 62
+ {len, len}
+ end
+ else
+ len = precision + (negative ? 1 : 0)
+ String.new(len + 1) do |buffer|
+ # e.g. precision = 13, count = 8
+ # "_____12345678\0" for positive
+ # "_____-12345678\0" for negative
+ buffer[len - 1] = 0
+ start = buffer + precision - count
+ LibGMP.get_str(start, upcase ? -base : base, self)
+
+ # `sizeinbase` may be 1 greater than the exact value
+ if buffer[len - 1] == 0
+ # e.g. precision = 7, count = 3, exact count = 2
+ # "____85\0\0" -> "____885\0" for positive
+ # "____-85\0\0" -> "____-885\0" for negative
+ # `start` will be zero-filled later
+ count -= 1
+ start += 1 if negative
+ start.move_to(start + 1, count)
+ end
+
+ base62_swapcase(Slice.new(buffer + len - count, count)) if base == 62
+
+ if negative
+ buffer.value = '-'.ord.to_u8
+ buffer += 1
+ end
+ Slice.new(buffer, precision - count).fill('0'.ord.to_u8)
+
+ {len, len}
+ end
+ end
+ end
end
- # :ditto:
- def to_s(io : IO) : Nil
- str = to_cstr
- io.write_utf8 Slice.new(str, LibC.strlen(str))
+ def to_s(io : IO, base : Int = 10, *, precision : Int = 1, upcase : Bool = false) : Nil
+ raise ArgumentError.new("Invalid base #{base}") unless 2 <= base <= 36 || base == 62
+ raise ArgumentError.new("upcase must be false for base 62") if upcase && base == 62
+ raise ArgumentError.new("Precision must be non-negative") unless precision >= 0
+
+ case {self, precision}
+ when {0, 0}
+ # do nothing
+ when {0, 1}
+ io << '0'
+ when {1, 1}
+ io << '1'
+ else
+ count = LibGMP.sizeinbase(self, base).to_i
+ ptr = LibGMP.get_str(nil, upcase ? -base : base, self)
+ negative = self < 0
+
+ # `sizeinbase` may be 1 greater than the exact value
+ count -= 1 if ptr[count + (negative ? 0 : -1)] == 0
+
+ if precision <= count
+ buffer = Slice.new(ptr, count + (negative ? 1 : 0))
+ else
+ if negative
+ io << '-'
+ ptr += 1 # this becomes the absolute value
+ end
+
+ (precision - count).times { io << '0' }
+ buffer = Slice.new(ptr, count)
+ end
+
+ base62_swapcase(buffer) if base == 62
+ io.write_string buffer
+ end
end
- # Returns a string containing the representation of big radix base (2 through 36).
- #
- # ```
- # require "big"
- #
- # BigInt.new("123456789101101987654321").to_s(8) # => "32111154373025463465765261"
- # BigInt.new("123456789101101987654321").to_s(16) # => "1a249b1f61599cd7eab1"
- # BigInt.new("123456789101101987654321").to_s(36) # => "k3qmt029k48nmpd"
- # ```
- def to_s(base : Int) : String
- raise ArgumentError.new("Invalid base #{base}") unless 2 <= base <= 36
- cstr = LibGMP.get_str(nil, base, self)
- String.new(cstr)
+ private def base62_swapcase(buffer)
+ buffer.map! do |x|
+ # for ASCII integers as returned by GMP the only possible characters are
+ # '\0', '-', '0'..'9', 'A'..'Z', and 'a'..'z'
+ if x & 0x40 != 0 # 'A'..'Z', 'a'..'z'
+ x ^ 0x20
+ else # '\0', '-', '0'..'9'
+ x
+ end
+ end
end
# :nodoc:
@@ -606,10 +699,6 @@ struct BigInt < Int
pointerof(@mpz)
end
- private def to_cstr
- LibGMP.get_str(nil, 10, mpz)
- end
-
def to_unsafe
mpz
end
diff --git a/src/big/big_rational.cr b/src/big/big_rational.cr
index e8cc2008999b..a2a3e884ede8 100644
--- a/src/big/big_rational.cr
+++ b/src/big/big_rational.cr
@@ -259,7 +259,7 @@ struct BigRational < Number
def to_s(io : IO, base : Int = 10) : Nil
str = to_cstr(base)
- io.write_utf8 Slice.new(str, LibC.strlen(str))
+ io.write_string Slice.new(str, LibC.strlen(str))
end
def inspect : String
diff --git a/src/big/json.cr b/src/big/json.cr
index 44d4ba6726dc..26682c6e3698 100644
--- a/src/big/json.cr
+++ b/src/big/json.cr
@@ -3,7 +3,7 @@ require "big"
class JSON::Builder
# Writes a big decimal.
- def number(number : BigDecimal)
+ def number(number : BigDecimal) : Nil
scalar do
@io << number
end
@@ -26,7 +26,7 @@ struct BigInt
to_s
end
- def to_json(json : JSON::Builder)
+ def to_json(json : JSON::Builder) : Nil
json.number(self)
end
end
@@ -56,7 +56,7 @@ struct BigFloat
to_s
end
- def to_json(json : JSON::Builder)
+ def to_json(json : JSON::Builder) : Nil
json.number(self)
end
end
@@ -86,7 +86,7 @@ struct BigDecimal
to_s
end
- def to_json(json : JSON::Builder)
+ def to_json(json : JSON::Builder) : Nil
json.number(self)
end
end
diff --git a/src/bit_array.cr b/src/bit_array.cr
index 414f3159483a..bab6764eae51 100644
--- a/src/bit_array.cr
+++ b/src/bit_array.cr
@@ -121,7 +121,7 @@ struct BitArray
bits = @bits[0]
bits >>= start
- bits &= (1 << count) - 1
+ bits &= ~(UInt32::MAX << count)
BitArray.new(count).tap { |ba| ba.@bits[0] = bits }
elsif size <= 64
@@ -129,10 +129,10 @@ struct BitArray
bits = @bits.as(UInt64*)[0]
bits >>= start
- bits &= (1 << count) - 1
+ bits &= ~(UInt64::MAX << count)
if count <= 32
- BitArray.new(count).tap { |ba| ba.@bits[0] = bits.to_u32 }
+ BitArray.new(count).tap { |ba| ba.@bits[0] = bits.to_u32! }
else
BitArray.new(count).tap { |ba| ba.@bits.as(UInt64*)[0] = bits }
end
@@ -150,7 +150,7 @@ struct BitArray
bits = @bits[start_bit_index + i + 1]
high_bits = bits
- high_bits &= (1 << start_sub_index) - 1
+ high_bits &= ~(UInt32::MAX << start_sub_index)
high_bits <<= 32 - start_sub_index
ba.@bits[i] = low_bits | high_bits
@@ -164,10 +164,13 @@ struct BitArray
end
end
- # Toggles the bit at the given *index*. A false bit becomes a `true` bit, and
- # vice versa.
- # Negative indices can be used to start counting from the end of the array.
- # Raises `IndexError` if trying to access a bit outside the array's range.
+ # Toggles the bit at the given *index*. A `false` bit becomes a `true` bit,
+ # and vice versa.
+ #
+ # Negative indices count backward from the end of the array (-1 is the last
+ # element).
+ #
+ # Raises `IndexError` if *index* is out of range.
#
# ```
# require "bit_array"
@@ -177,11 +180,72 @@ struct BitArray
# ba.toggle(3)
# ba[3] # => true
# ```
- def toggle(index)
+ def toggle(index) : Nil
bit_index, sub_index = bit_index_and_sub_index(index)
@bits[bit_index] ^= 1 << sub_index
end
+ # Toggles all bits that are within the given *range*. A `false` bit becomes a
+ # `true` bit, and vice versa.
+ #
+ # Negative indices count backward from the end of the array (-1 is the last
+ # element).
+ #
+ # Raises `IndexError` if the starting index is out of range.
+ #
+ # ```
+ # require "bit_array"
+ #
+ # ba = BitArray.new(5)
+ # ba.to_s # => "BitArray[00000]"
+ # ba.toggle(1..-2)
+ # ba.to_s # => "BitArray[01110]"
+ # ```
+ def toggle(range : Range)
+ toggle(*Indexable.range_to_index_and_count(range, size) || raise IndexError.new)
+ end
+
+ # Toggles *count* or less (if there aren't enough) bits starting at the given
+ # *start* index. A `false` bit becomes a `true` bit, and vice versa.
+ #
+ # Negative indices count backward from the end of the array (-1 is the last
+ # element).
+ #
+ # Raises `IndexError` if *index* is out of range.
+ # Raises `ArgumentError` if *count* is a negative number.
+ #
+ # ```
+ # require "bit_array"
+ #
+ # ba = BitArray.new(5)
+ # ba.to_s # => "BitArray[00000]"
+ # ba.toggle(1, 3)
+ # ba.to_s # => "BitArray[01110]"
+ # ```
+ def toggle(start : Int, count : Int)
+ start, count = normalize_start_and_count(start, count)
+
+ start_bit_index, start_sub_index = start.divmod(32)
+ end_bit_index, end_sub_index = (start + count - 1).divmod(32)
+
+ if start_bit_index == end_bit_index
+ # same UInt32, don't perform the loop at all
+ @bits[start_bit_index] ^= uint32_mask(start_sub_index, end_sub_index)
+ else
+ @bits[start_bit_index] ^= uint32_mask(start_sub_index, 31)
+ (start_bit_index + 1..end_bit_index - 1).each do |i|
+ @bits[i] = ~@bits[i]
+ end
+ @bits[end_bit_index] ^= uint32_mask(0, end_sub_index)
+ end
+ end
+
+ # returns (1 << from) | (1 << (from + 1)) | ... | (1 << to)
+ @[AlwaysInline]
+ private def uint32_mask(from, to)
+ (Int32::MIN >> (to - from)).to_u32! >> (31 - to)
+ end
+
# Inverts all bits in the array. Falses become `true` and vice versa.
#
# ```
@@ -193,7 +257,7 @@ struct BitArray
# ba.invert
# ba # => BitArray[11001]
# ```
- def invert
+ def invert : Nil
malloc_size.times do |i|
@bits[i] = ~@bits[i]
end
@@ -259,7 +323,7 @@ struct BitArray
protected def clear_unused_bits
# There are no unused bits if `size` is a multiple of 32.
bit_index, sub_index = @size.divmod(32)
- @bits[bit_index] &= (1 << sub_index) - 1 unless sub_index == 0
+ @bits[bit_index] &= ~(UInt32::MAX << sub_index) unless sub_index == 0
end
private def bytesize
diff --git a/src/char.cr b/src/char.cr
index f081318335df..319de3188e73 100644
--- a/src/char.cr
+++ b/src/char.cr
@@ -125,8 +125,8 @@ struct Char
# Performs a `#step` in the direction of the _limit_. For instance:
#
# ```
- # 'd'.step(to: 'a').to_a # => ['d', 'c', 'b', 'a']
- # 'a'.step(to: 'd').to_a # => ['a', 'b', 'c', 'd']
+ # 'd'.step(to: 'a').to_a # => ['d', 'c', 'b', 'a']
+ # 'a'.step(to: 'd').to_a # => ['a', 'b', 'c', 'd']
# ```
def step(*, to limit = nil, exclusive : Bool = false, &)
if limit
@@ -799,7 +799,7 @@ struct Char
# Optimization: writing a slice is much slower than writing a byte
if io.has_non_utf8_encoding?
- io.write_utf8 Slice.new(pointerof(byte), 1)
+ io.write_string Slice.new(pointerof(byte), 1)
else
io.write_byte byte
end
@@ -810,7 +810,7 @@ struct Char
chars[i] = byte
i += 1
end
- io.write_utf8 chars.to_slice[0, i]
+ io.write_string chars.to_slice[0, i]
end
end
diff --git a/src/compiler/crystal/codegen/call.cr b/src/compiler/crystal/codegen/call.cr
index 4523c7a965ef..22366b095874 100644
--- a/src/compiler/crystal/codegen/call.cr
+++ b/src/compiler/crystal/codegen/call.cr
@@ -586,12 +586,12 @@ class Crystal::CodeGenVisitor
abi_arg_type = abi_info.arg_types[i]?
if abi_arg_type && (attr = abi_arg_type.attr)
- @last.add_instruction_attribute(i + arg_offset, attr, llvm_context)
+ @last.add_instruction_attribute(i + arg_offset, attr, llvm_context, abi_arg_type.type)
end
end
if sret
- @last.add_instruction_attribute(1, LLVM::Attribute::StructRet, llvm_context)
+ @last.add_instruction_attribute(1, LLVM::Attribute::StructRet, llvm_context, abi_info.return_type.type)
end
end
@@ -605,7 +605,7 @@ class Crystal::CodeGenVisitor
arg_types = fun_type.try(&.arg_types) || target_def.try &.args.map &.type
arg_types.try &.each_with_index do |arg_type, i|
if abi_info && (abi_arg_type = abi_info.arg_types[i]?) && (attr = abi_arg_type.attr)
- @last.add_instruction_attribute(i + arg_offset, attr, llvm_context)
+ @last.add_instruction_attribute(i + arg_offset, attr, llvm_context, abi_arg_type.type)
end
end
end
diff --git a/src/compiler/crystal/codegen/class_var.cr b/src/compiler/crystal/codegen/class_var.cr
index b9573b9d54af..d2979dcf161d 100644
--- a/src/compiler/crystal/codegen/class_var.cr
+++ b/src/compiler/crystal/codegen/class_var.cr
@@ -92,6 +92,7 @@ class Crystal::CodeGenVisitor
global = declare_class_var(class_var)
global = ensure_class_var_in_this_module(global, class_var)
if init_func
+ set_current_debug_location initializer.node if @debug.line_numbers?
call init_func
end
return global
@@ -121,6 +122,8 @@ class Crystal::CodeGenVisitor
discard = false
new_func = in_main do
define_main_function(init_function_name, ([] of LLVM::Type), llvm_context.void, needs_alloca: true) do |func|
+ set_internal_fun_debug_location(func, init_function_name, node.location)
+
with_cloned_context do
# "self" in a constant is the class_var owner
context.type = class_var.owner
@@ -223,6 +226,8 @@ class Crystal::CodeGenVisitor
def create_read_virtual_class_var_ptr_function(fun_name, class_var, owner)
in_main do
define_main_function(fun_name, [llvm_context.int32], llvm_type(class_var.type).pointer) do |func|
+ set_internal_fun_debug_location(func, fun_name)
+
self_type_id = func.params[0]
cmp = equal?(self_type_id, type_id(owner.base_type))
@@ -268,6 +273,8 @@ class Crystal::CodeGenVisitor
def create_read_virtual_metaclass_var_ptr_function(fun_name, class_var, owner)
in_main do
define_main_function(fun_name, [llvm_context.int32], llvm_type(class_var.type).pointer) do |func|
+ set_internal_fun_debug_location(func, fun_name)
+
self_type_id = func.params[0]
cmp = equal?(self_type_id, type_id(owner.base_type.metaclass))
@@ -313,6 +320,7 @@ class Crystal::CodeGenVisitor
in_main do
define_main_function(fun_name, ([] of LLVM::Type), llvm_type(class_var.type).pointer) do |func|
+ set_internal_fun_debug_location(func, fun_name, initializer.node.location)
init_func = check_main_fun init_func.name, init_func
ret lazy_initialize_class_var(initializer.node, init_func, global, initialized_flag)
end
diff --git a/src/compiler/crystal/codegen/codegen.cr b/src/compiler/crystal/codegen/codegen.cr
index 2e2610b483b6..bcd0932f31b1 100644
--- a/src/compiler/crystal/codegen/codegen.cr
+++ b/src/compiler/crystal/codegen/codegen.cr
@@ -189,8 +189,6 @@ module Crystal
@personality_name = "__crystal_personality"
end
- emit_main_def_debug_metadata(@main, "??") unless @debug.none?
-
@context = Context.new @main, @program
@context.return_type = @main_ret_type
@@ -207,6 +205,8 @@ module Crystal
@modules = {"" => @main_module_info} of String => ModuleInfo
@types_to_modules = {} of Type => ModuleInfo
+ set_internal_fun_debug_location(@main, MAIN_NAME, nil)
+
@alloca_block, @entry_block = new_entry_block_chain "alloca", "entry"
@in_lib = false
@@ -537,7 +537,7 @@ module Crystal
end
get_global class_var_global_name(node_exp.var), node_exp.type, node_exp.var
when Global
- get_global node_exp.name, node_exp.type, node_exp.var
+ node.raise "BUG: there should be no use of global variables other than $~ and $?"
when Path
# Make sure the constant is initialized before taking a pointer of it
const = node_exp.target_const.not_nil!
@@ -574,6 +574,7 @@ module Crystal
the_fun = codegen_fun fun_literal_name, node.def, context.type, fun_module_info: @main_module_info, is_fun_literal: true, is_closure: is_closure
the_fun = check_main_fun fun_literal_name, the_fun
+ set_current_debug_location(node) if @debug.line_numbers?
fun_ptr = bit_cast(the_fun, llvm_context.void_pointer)
if is_closure
ctx_ptr = bit_cast(context.closure_ptr.not_nil!, llvm_context.void_pointer)
@@ -1017,7 +1018,7 @@ module Crystal
when InstanceVar
instance_var_ptr context.type, target.name, llvm_self_ptr
when Global
- get_global target.name, target_type, target.var
+ node.raise "BUG: there should be no use of global variables other than $~ and $?"
when ClassVar
read_class_var_ptr(target)
when Var
@@ -1119,6 +1120,7 @@ module Crystal
unless thread_local_fun
thread_local_fun = in_main do
define_main_function(fun_name, [llvm_type(type).pointer.pointer], llvm_context.void) do |func|
+ set_internal_fun_debug_location(func, fun_name, real_var.location)
builder.store get_global_var(name, type, real_var), func.params[0]
builder.ret
end
@@ -1143,15 +1145,7 @@ module Crystal
codegen_assign(var, value, node)
end
when Global
- if value = node.value
- request_value do
- accept value
- end
-
- ptr = get_global var.name, var.type, var.var
- assign ptr, var.type, value.type, @last
- return false
- end
+ node.raise "BUG: there should be no use of global variables other than $~ and $?"
when ClassVar
# This is the case of a class var initializer
initialize_class_var(var)
@@ -1208,18 +1202,13 @@ module Crystal
end
def visit(node : Global)
- read_global node.name.to_s, node.type, node.var
+ node.raise "BUG: there should be no use of global variables other than $~ and $?"
end
def visit(node : ClassVar)
@last = read_class_var(node)
end
- def read_global(name, type, real_var)
- @last = get_global name, type, real_var
- @last = to_lhs @last, type
- end
-
def visit(node : InstanceVar)
read_instance_var node.type, context.type, node.name, llvm_self_ptr
end
@@ -1608,6 +1597,8 @@ module Crystal
def create_check_proc_is_not_closure_fun(fun_name)
in_main do
define_main_function(fun_name, [llvm_typer.proc_type], llvm_context.void_pointer) do |func|
+ set_internal_fun_debug_location(func, fun_name)
+
param = func.params.first
fun_ptr = extract_value param, 0
@@ -1706,6 +1697,16 @@ module Crystal
end
end
+ # used for generated internal functions like `~metaclass` and `~match`
+ def set_internal_fun_debug_location(func, name, location = nil)
+ return if @debug.none?
+ location ||= UNKNOWN_LOCATION
+ emit_fun_debug_metadata(func, name, location)
+ set_current_debug_location(location) if @debug.line_numbers?
+ end
+
+ private UNKNOWN_LOCATION = Location.new("??", 0, 0)
+
def llvm_self(type = context.type)
self_var = context.vars["self"]?
if self_var
@@ -2255,7 +2256,7 @@ module Crystal
end
def visit(node : ExpandableNode)
- raise "BUG: #{node} at #{node.location} should have been expanded"
+ raise "BUG: #{node} (#{node.class}) at #{node.location} should have been expanded"
end
def visit(node : ASTNode)
diff --git a/src/compiler/crystal/codegen/const.cr b/src/compiler/crystal/codegen/const.cr
index 48a053619a60..80b91f2b7170 100644
--- a/src/compiler/crystal/codegen/const.cr
+++ b/src/compiler/crystal/codegen/const.cr
@@ -72,6 +72,8 @@ class Crystal::CodeGenVisitor
end
def initialize_simple_const(const)
+ set_current_debug_location const.locations.try &.first? if @debug.line_numbers?
+
global = declare_const(const)
request_value do
accept const.value
@@ -100,6 +102,8 @@ class Crystal::CodeGenVisitor
# Start with fresh variables
context.vars = LLVMVars.new
+ set_current_debug_location const.locations.try &.first? if @debug.line_numbers?
+
alloca_vars const.fake_def.try(&.vars), const.fake_def
request_value do
accept const.value
@@ -146,6 +150,8 @@ class Crystal::CodeGenVisitor
in_main do
define_main_function(fun_name, ([] of LLVM::Type), llvm_context.void, needs_alloca: true) do |func|
+ set_internal_fun_debug_location(func, fun_name, const.locations.try &.first?)
+
with_cloned_context do
# "self" in a constant is the constant's namespace
context.type = const.namespace
@@ -228,6 +234,7 @@ class Crystal::CodeGenVisitor
def create_read_const_function(fun_name, const)
in_main do
define_main_function(fun_name, ([] of LLVM::Type), llvm_type(const.value.type).pointer) do |func|
+ set_internal_fun_debug_location(func, fun_name, const.locations.try &.first?)
global = initialize_const(const)
ret global
end
diff --git a/src/compiler/crystal/codegen/debug.cr b/src/compiler/crystal/codegen/debug.cr
index fb80af027025..8181f40ea7ee 100644
--- a/src/compiler/crystal/codegen/debug.cr
+++ b/src/compiler/crystal/codegen/debug.cr
@@ -451,25 +451,25 @@ module Crystal
builder.set_current_debug_location(0, 0, nil)
end
- def emit_main_def_debug_metadata(main_fun, filename)
+ def emit_fun_debug_metadata(func, fun_name, location, *, debug_types = [] of LibLLVMExt::Metadata, is_optimized = false)
+ filename = location.try(&.original_filename) || "??"
+ line_number = location.try(&.line_number) || 0
+
file, dir = file_and_dir(filename)
scope = di_builder.create_file(file, dir)
- fn_metadata = di_builder.create_function(scope, MAIN_NAME, MAIN_NAME, scope,
- 0, fun_metadata_type, true, true, 0, LLVM::DIFlags::Zero, false, main_fun)
- fun_metadatas[main_fun] = [FunMetadata.new(filename || "??", fn_metadata)]
+ fn_metadata = di_builder.create_function(scope, fun_name, fun_name, scope,
+ line_number, fun_metadata_type(debug_types), true, true,
+ line_number, LLVM::DIFlags::Zero, is_optimized, func)
+ fun_metadatas[func] = [FunMetadata.new(filename, fn_metadata)]
end
def emit_def_debug_metadata(target_def)
location = target_def.location.try &.expanded_location
return unless location
- file, dir = file_and_dir(location.filename)
- scope = di_builder.create_file(file, dir)
- is_optimised = !@debug.variables?
- fn_metadata = di_builder.create_function(scope, target_def.name, target_def.name, scope,
- location.line_number, fun_metadata_type(context.fun_debug_params), true, true,
- location.line_number, LLVM::DIFlags::Zero, is_optimised, context.fun)
- fun_metadatas[context.fun] = [FunMetadata.new(location.original_filename || "??", fn_metadata)]
+ emit_fun_debug_metadata(context.fun, target_def.name, location,
+ debug_types: context.fun_debug_params,
+ is_optimized: !@debug.variables?)
end
def declare_debug_for_function_argument(arg_name, arg_type, arg_no, alloca, location)
diff --git a/src/compiler/crystal/codegen/fun.cr b/src/compiler/crystal/codegen/fun.cr
index 38609fc93a01..dbcc0964bd05 100644
--- a/src/compiler/crystal/codegen/fun.cr
+++ b/src/compiler/crystal/codegen/fun.cr
@@ -84,6 +84,7 @@ class Crystal::CodeGenVisitor
needs_body = !target_def.is_a?(External) || is_exported_fun
if needs_body
emit_def_debug_metadata target_def unless @debug.none?
+ set_current_debug_location target_def if @debug.line_numbers?
context.fun.add_attribute LLVM::Attribute::UWTable
if @program.has_flag?("darwin")
@@ -365,7 +366,7 @@ class Crystal::CodeGenVisitor
abi_arg_type = abi_info.arg_types[i]
if attr = abi_arg_type.attr
- context.fun.add_attribute(attr, i + offset + 1)
+ context.fun.add_attribute(attr, i + offset + 1, abi_arg_type.type)
end
i += 1 unless abi_arg_type.kind == LLVM::ABI::ArgKind::Ignore
@@ -373,7 +374,7 @@ class Crystal::CodeGenVisitor
# This is for sret
if (attr = abi_info.return_type.attr) && attr == LLVM::Attribute::StructRet
- context.fun.add_attribute(attr, 1)
+ context.fun.add_attribute(attr, 1, abi_info.return_type.type)
end
args
diff --git a/src/compiler/crystal/codegen/link.cr b/src/compiler/crystal/codegen/link.cr
index 86f021a4247b..13994aa32905 100644
--- a/src/compiler/crystal/codegen/link.cr
+++ b/src/compiler/crystal/codegen/link.cr
@@ -81,12 +81,20 @@ module Crystal
end
class CrystalLibraryPath
+ def self.default_paths : Array(String)
+ paths = ENV.fetch("CRYSTAL_LIBRARY_PATH", Crystal::Config.library_path).split(Process::PATH_DELIMITER, remove_empty: true)
+
+ CrystalPath.expand_paths(paths)
+
+ paths
+ end
+
def self.default_path : String
- ENV.fetch("CRYSTAL_LIBRARY_PATH", Crystal::Config.library_path)
+ default_paths.join(Process::PATH_DELIMITER)
end
class_getter paths : Array(String) do
- default_path.split(Process::PATH_DELIMITER, remove_empty: true)
+ default_paths
end
end
diff --git a/src/compiler/crystal/codegen/match.cr b/src/compiler/crystal/codegen/match.cr
index 8e0cc646c0e3..67bf74dd8c9b 100644
--- a/src/compiler/crystal/codegen/match.cr
+++ b/src/compiler/crystal/codegen/match.cr
@@ -47,6 +47,7 @@ class Crystal::CodeGenVisitor
private def create_match_fun(name, type)
in_main do
define_main_function(name, ([llvm_context.int32]), llvm_context.int1) do |func|
+ set_internal_fun_debug_location(func, name)
type_id = func.params.first
create_match_fun_body(type, type_id)
end
diff --git a/src/compiler/crystal/codegen/primitives.cr b/src/compiler/crystal/codegen/primitives.cr
index c0c79aadf11c..281b1374f813 100644
--- a/src/compiler/crystal/codegen/primitives.cr
+++ b/src/compiler/crystal/codegen/primitives.cr
@@ -610,7 +610,7 @@ class Crystal::CodeGenVisitor
when from_type.normal_rank == to_type.normal_rank
# if the normal_rank is the same (eg: UInt64 / Int64)
# there is still chance for overflow
- if checked
+ if from_type.kind != to_type.kind && checked
overflow = codegen_out_of_range(to_type, from_type, arg)
codegen_raise_overflow_cond(overflow)
end
@@ -916,6 +916,8 @@ class Crystal::CodeGenVisitor
in_main do
define_main_function(name, ([llvm_context.int32]), llvm_context.int32) do |func|
+ set_internal_fun_debug_location(func, name)
+
arg = func.params.first
current_block = insert_block
diff --git a/src/compiler/crystal/command.cr b/src/compiler/crystal/command.cr
index 7f73093bccad..70aab5f366e3 100644
--- a/src/compiler/crystal/command.cr
+++ b/src/compiler/crystal/command.cr
@@ -190,7 +190,7 @@ class Crystal::Command
private def hierarchy
config, result = compile_no_codegen "tool hierarchy", hierarchy: true, top_level: true
@progress_tracker.stage("Tool (hierarchy)") do
- Crystal.print_hierarchy result.program, config.hierarchy_exp, config.output_format
+ Crystal.print_hierarchy result.program, STDOUT, config.hierarchy_exp, config.output_format
end
end
diff --git a/src/compiler/crystal/crystal_path.cr b/src/compiler/crystal/crystal_path.cr
index 5cb2ffae8fd7..e79beabb8ea5 100644
--- a/src/compiler/crystal/crystal_path.cr
+++ b/src/compiler/crystal/crystal_path.cr
@@ -13,22 +13,61 @@ module Crystal
private DEFAULT_LIB_PATH = "lib"
- def self.default_path
- ENV["CRYSTAL_PATH"]? || begin
- if Crystal::Config.path.blank?
- DEFAULT_LIB_PATH
- elsif Crystal::Config.path.split(Process::PATH_DELIMITER).includes?(DEFAULT_LIB_PATH)
- Crystal::Config.path
+ def self.default_paths : Array(String)
+ if path = ENV["CRYSTAL_PATH"]?
+ path_array = path.split(Process::PATH_DELIMITER, remove_empty: true)
+ elsif path = Crystal::Config.path.presence
+ path_array = path.split(Process::PATH_DELIMITER, remove_empty: true)
+ unless path_array.includes?(DEFAULT_LIB_PATH)
+ path_array.unshift DEFAULT_LIB_PATH
+ end
+ else
+ path_array = [DEFAULT_LIB_PATH]
+ end
+
+ expand_paths(path_array)
+
+ path_array
+ end
+
+ def self.default_path : String
+ default_paths.join(Process::PATH_DELIMITER)
+ end
+
+ # Expand `$ORIGIN` in the paths to the directory where the compiler binary
+ # is located (at runtime).
+ # For install locations like
+ # `/path/prefix/bin/crystal` for the compiler
+ # `/path/prefix/share/crystal/src` for the standard library
+ # the path `$ORIGIN/../share/crystal/src` resolves to
+ # the standard library location.
+ # This generic path can be passed into the compiler via CRYSTAL_CONFIG_PATH
+ # to produce a portable binary that resolves the standard library path
+ # relative to the compiler location, independent of the absolute path.
+ def self.expand_paths(paths, origin)
+ paths.map! do |path|
+ if (chopped = path.lchop?("$ORIGIN")) && chopped[0].in?(::Path::SEPARATORS)
+ if origin.nil?
+ raise "Missing executable path to expand $ORIGIN path"
+ end
+ File.join(origin, chopped)
else
- {DEFAULT_LIB_PATH, Crystal::Config.path}.join(Process::PATH_DELIMITER)
+ path
end
end
end
+ def self.expand_paths(paths)
+ origin = nil
+ if executable_path = Process.executable_path
+ origin = File.dirname(executable_path)
+ end
+ expand_paths(paths, origin)
+ end
+
property entries : Array(String)
- def initialize(path = CrystalPath.default_path, codegen_target = Config.host_target)
- @entries = path.split(Process::PATH_DELIMITER).reject &.empty?
+ def initialize(@entries : Array(String) = CrystalPath.default_paths, codegen_target = Config.host_target)
add_target_path(codegen_target)
end
diff --git a/src/compiler/crystal/exception.cr b/src/compiler/crystal/exception.cr
index d6e19227236d..f5af99742001 100644
--- a/src/compiler/crystal/exception.cr
+++ b/src/compiler/crystal/exception.cr
@@ -140,10 +140,13 @@ module Crystal
decorator = line_number_decorator(line_number)
lstripped_line = line.lstrip
space_delta = line.chars.size - lstripped_line.chars.size
+ # Column number should start at `1`. We're using `0` to track bogus passed
+ # `column_number`.
+ final_column_number = (column_number - space_delta).clamp(0..)
io << "\n\n"
io << colorize(decorator).dim << colorize(lstripped_line.chomp).bold
- append_error_indicator(io, decorator.chars.size, column_number - space_delta, size || 0)
+ append_error_indicator(io, decorator.chars.size, final_column_number, size || 0)
end
end
diff --git a/src/compiler/crystal/program.cr b/src/compiler/crystal/program.cr
index 0a52fdcfe2f1..722501add55e 100644
--- a/src/compiler/crystal/program.cr
+++ b/src/compiler/crystal/program.cr
@@ -26,10 +26,6 @@ module Crystal
# All symbols (:foo, :bar) found in the program
getter symbols = Set(String).new
- # All global variables in the program ($foo, $bar), indexed by their name.
- # The names includes the `$` sign.
- getter global_vars = {} of String => MetaTypeVar
-
# Hash that prevents recursive splat expansions. For example:
#
# ```
@@ -123,7 +119,7 @@ module Crystal
property compiler : Compiler?
def initialize
- super(self, self, "top_level")
+ super(self, self, "main")
# Every crystal program comes with some predefined types that we initialize here,
# like Object, Value, Reference, etc.
diff --git a/src/compiler/crystal/semantic/cleanup_transformer.cr b/src/compiler/crystal/semantic/cleanup_transformer.cr
index a7b48cca4fa7..864e09f7e062 100644
--- a/src/compiler/crystal/semantic/cleanup_transformer.cr
+++ b/src/compiler/crystal/semantic/cleanup_transformer.cr
@@ -265,7 +265,7 @@ module Crystal
if target.is_a?(Path)
const = target.target_const.not_nil!
- return node unless const.used?
+ return node if !const.used? || const.cleaned_up?
unless const.value.type?
node.raise "can't infer type of constant #{const} (maybe the constant refers to itself?)"
@@ -285,6 +285,7 @@ module Crystal
if target.is_a?(Path)
const = const.not_nil!
const.value = const.value.transform self
+ const.cleaned_up = true
end
if node.target == node.value
@@ -301,6 +302,18 @@ module Crystal
node
end
+ def transform(node : Path)
+ # Some constants might not have been cleaned up at this point because
+ # they don't have an explicit `Assign` node. One example is regex
+ # literals: a constant is created for them, but there's no `Assign` node.
+ if (const = node.target_const) && const.used? && !const.cleaned_up?
+ const.value = const.value.transform self
+ const.cleaned_up = true
+ end
+
+ node
+ end
+
private def void_lib_call?(node)
return unless node.is_a?(Call)
diff --git a/src/compiler/crystal/semantic/exception.cr b/src/compiler/crystal/semantic/exception.cr
index 394be93baf3f..1b367f3ddb97 100644
--- a/src/compiler/crystal/semantic/exception.cr
+++ b/src/compiler/crystal/semantic/exception.cr
@@ -314,25 +314,6 @@ module Crystal
end
class Program
- def undefined_global_variable(node, similar_name)
- common = String.build do |str|
- str << "can't infer the type of global variable '#{node.name}'"
- if similar_name
- str << '\n'
- str << colorize(" (did you mean #{similar_name}?)").yellow.bold.to_s
- end
- end
-
- msg = String.build do |str|
- str << common
- str << "\n\n"
- str << undefined_variable_message("global", node.name)
- str << "\n\n"
- str << common
- end
- node.raise msg
- end
-
def undefined_class_variable(node, owner, similar_name)
common = String.build do |str|
str << "can't infer the type of class variable '#{node.name}' of #{owner.devirtualize}"
diff --git a/src/compiler/crystal/semantic/literal_expander.cr b/src/compiler/crystal/semantic/literal_expander.cr
index c3d34946b6f1..57dd8b43e767 100644
--- a/src/compiler/crystal/semantic/literal_expander.cr
+++ b/src/compiler/crystal/semantic/literal_expander.cr
@@ -253,18 +253,16 @@ module Crystal
#
# /regex/flags
#
- # To:
+ # To declaring a constant with this value (if not already declared):
#
- # if temp_var = $some_global
- # temp_var
- # else
- # $some_global = Regex.new("regex", Regex::Options.new(flags))
- # end
+ # ```
+ # Regex.new("regex", Regex::Options.new(flags))
+ # ```
#
- # That is, cache the regex in a global variable.
+ # and then reading from that constant.
+ # That is, we cache regex literals to avoid recompiling them all of the time.
#
# Only do this for regex literals that don't contain interpolation.
- #
# If there's an interpolation, expand to: Regex.new(interpolation, flags)
def expand(node : RegexLiteral)
node_value = node.value
@@ -273,30 +271,19 @@ module Crystal
string = node_value.value
key = {string, node.options}
- index = @regexes.index key
- unless index
- index = @regexes.size
- @regexes << key
- end
-
- global_name = "$Regex:#{index}"
- temp_name = @program.new_temp_var_name
+ index = @regexes.index(key) || @regexes.size
+ const_name = "$Regex:#{index}"
- global_var = MetaTypeVar.new(global_name)
- global_var.owner = @program
- type = @program.nilable(@program.regex)
- global_var.freeze_type = type
- global_var.type = type
+ if index == @regexes.size
+ @regexes << key
- # TODO: need to bind with nil_var for codegen, but shouldn't be needed
- global_var.bind_to(@program.nil_var)
+ const_value = regex_new_call(node, StringLiteral.new(string).at(node))
+ const = Const.new(@program, @program, const_name, const_value)
- @program.global_vars[global_name] = global_var
+ @program.types[const_name] = const
+ end
- first_assign = Assign.new(Var.new(temp_name).at(node), Global.new(global_name).at(node)).at(node)
- regex = regex_new_call(node, StringLiteral.new(string).at(node))
- second_assign = Assign.new(Global.new(global_name).at(node), regex).at(node)
- If.new(first_assign, Var.new(temp_name).at(node), second_assign).at(node)
+ Path.new(const_name)
else
regex_new_call(node, node_value)
end
diff --git a/src/compiler/crystal/semantic/main_visitor.cr b/src/compiler/crystal/semantic/main_visitor.cr
index cf8d75a3513e..a20336a59102 100644
--- a/src/compiler/crystal/semantic/main_visitor.cr
+++ b/src/compiler/crystal/semantic/main_visitor.cr
@@ -87,6 +87,7 @@ module Crystal
property last_block_kind : Symbol?
property? inside_ensure : Bool = false
property? inside_constant = false
+ property file_module : FileModule?
@unreachable = false
@is_initialize = false
@@ -99,7 +100,6 @@ module Crystal
@found_self_in_initialize_call : Array(ASTNode)?
@used_ivars_in_calls_in_initialize : Hash(String, Array(ASTNode))?
@block_context : Block?
- @file_module : FileModule?
@while_vars : MetaVars?
# Type filters for `exp` in `!exp`, used after a `while`
@@ -423,22 +423,6 @@ module Crystal
class_var = lookup_class_var(var)
var.var = class_var
class_var.thread_local = true if thread_local
- when Global
- if @untyped_def
- node.raise "declaring the type of a global variable must be done at the class level"
- end
-
- thread_local = check_class_var_annotations
- if thread_local
- global_var = @program.global_vars[var.name]
- global_var.thread_local = true
- end
-
- if value = node.value
- type_assign(var, value, node)
- node.bind_to(var)
- return false
- end
else
raise "Bug: unexpected var type: #{var.class}"
end
@@ -586,39 +570,12 @@ module Crystal
node.bind_to expanded
node.expanded = expanded
else
- visit_global node
+ node.raise "BUG: there should be no use of global variables other than $~ and $?"
end
false
end
- def visit_global(node)
- var = lookup_global_variable(node)
-
- if first_time_accessing_meta_type_var?(var)
- var_type = var.type?
- if var_type && !var_type.includes_type?(program.nil)
- node.raise "global variable '#{node.name}' is read here before it was initialized, rendering it nilable, but its type is #{var_type}"
- end
- var.bind_to program.nil_var
- end
-
- node.bind_to var
- node.var = var
- var
- end
-
- def lookup_global_variable(node)
- var = program.global_vars[node.name]?
- undefined_global_variable(node) unless var
- var
- end
-
- def undefined_global_variable(node)
- similar_name = lookup_similar_global_variable_name(node)
- program.undefined_global_variable(node, similar_name)
- end
-
def undefined_instance_variable(owner, node)
similar_name = lookup_similar_instance_variable_name(node, owner)
program.undefined_instance_variable(node, owner, similar_name)
@@ -637,14 +594,6 @@ module Crystal
end
end
- def lookup_similar_global_variable_name(node)
- Levenshtein.find(node.name) do |finder|
- program.global_vars.each_key do |name|
- finder.test(name)
- end
- end
- end
-
def first_time_accessing_meta_type_var?(var)
return false if var.uninitialized?
@@ -924,26 +873,7 @@ module Crystal
end
def type_assign(target : Global, value, node)
- thread_local = check_class_var_annotations
-
- value.accept self
-
- var = lookup_global_variable(target)
-
- # If we are assigning to a global inside a method, make it nilable
- # if this is the first time we are assigning to it, because
- # the method might be called conditionally
- if @typed_def && first_time_accessing_meta_type_var?(var)
- var.bind_to program.nil_var
- end
-
- var.thread_local = true if thread_local
- target.var = var
-
- target.bind_to var
-
- node.bind_to value
- var.bind_to value
+ node.raise "BUG: there should be no use of global variables other than $~ and $?"
end
def type_assign(target : ClassVar, value, node)
@@ -1130,6 +1060,7 @@ module Crystal
block_visitor.parent = self
block_visitor.with_scope = node.scope || with_scope
block_visitor.exception_handler_vars = @exception_handler_vars
+ block_visitor.file_module = @file_module
block_scope = @scope
block_scope ||= current_type.metaclass unless current_type.is_a?(Program)
@@ -2490,7 +2421,7 @@ module Crystal
when ClassVar
visit_class_var exp
when Global
- visit_global exp
+ node.raise "BUG: there should be no use of global variables other than $~ and $?"
when Path
exp.accept self
if const = exp.target_const
diff --git a/src/compiler/crystal/semantic/match.cr b/src/compiler/crystal/semantic/match.cr
index 3779ed0fd5f6..329802a5ff6d 100644
--- a/src/compiler/crystal/semantic/match.cr
+++ b/src/compiler/crystal/semantic/match.cr
@@ -46,12 +46,10 @@ module Crystal
# Any instance variables associated with the method instantiation
getter free_vars : Hash(String, TypeVar)?
- getter? strict : Bool
-
# Def free variables, unbound (`def (X, Y) ...`)
property def_free_vars : Array(String)?
- def initialize(@instantiated_type, @defining_type, @free_vars = nil, @strict = false, @def_free_vars = nil)
+ def initialize(@instantiated_type, @defining_type, @free_vars = nil, @def_free_vars = nil)
end
def get_free_var(name)
@@ -93,7 +91,7 @@ module Crystal
end
def clone
- MatchContext.new(@instantiated_type, @defining_type, @free_vars.dup, @strict, @def_free_vars.dup)
+ MatchContext.new(@instantiated_type, @defining_type, @free_vars.dup, @def_free_vars.dup)
end
end
diff --git a/src/compiler/crystal/semantic/restrictions.cr b/src/compiler/crystal/semantic/restrictions.cr
index 08bb3fe7abd1..6bcf930c23b3 100644
--- a/src/compiler/crystal/semantic/restrictions.cr
+++ b/src/compiler/crystal/semantic/restrictions.cr
@@ -33,39 +33,39 @@ require "../types"
module Crystal
class ASTNode
- def restriction_of?(other : Underscore, owner, strict = false)
+ def restriction_of?(other : Underscore, owner)
true
end
- def restriction_of?(other : ASTNode, owner, strict = false)
+ def restriction_of?(other : ASTNode, owner)
self == other
end
- def restriction_of?(other : Type, owner, strict = false)
+ def restriction_of?(other : Type, owner)
false
end
- def restriction_of?(other, owner, strict = false)
+ def restriction_of?(other, owner)
raise "BUG: called #{self}.restriction_of?(#{other})"
end
end
class Self
- def restriction_of?(type : Type, owner, strict = false)
- owner.restriction_of?(type, owner, strict)
+ def restriction_of?(type : Type, owner)
+ owner.restriction_of?(type, owner)
end
- def restriction_of?(type : Self, owner, strict = false)
+ def restriction_of?(type : Self, owner)
true
end
- def restriction_of?(type : ASTNode, owner, strict = false)
+ def restriction_of?(type : ASTNode, owner)
false
end
end
struct DefWithMetadata
- def restriction_of?(other : DefWithMetadata, owner, strict = false)
+ def restriction_of?(other : DefWithMetadata, owner)
# This is how multiple defs are sorted by 'restrictions' (?)
# If one yields and the other doesn't, none is stricter than the other
@@ -118,7 +118,7 @@ module Crystal
# If this is a splat arg and the other not, this is not stricter than the other
return false if index == self.def.splat_index
- return false unless self_type.restriction_of?(other_type, owner, strict)
+ return false unless self_type.restriction_of?(other_type, owner)
end
end
@@ -131,7 +131,7 @@ module Crystal
if self_restriction && other_restriction
# If both splat have restrictions, check which one is stricter
- return false unless self_restriction.restriction_of?(other_restriction, owner, strict)
+ return false unless self_restriction.restriction_of?(other_restriction, owner)
elsif self_restriction
# If only self has a restriction, it's stricter than the other
return true
@@ -165,7 +165,7 @@ module Crystal
return false if self_restriction == nil && other_restriction != nil
if self_restriction && other_restriction
- return false unless self_restriction.restriction_of?(other_restriction, owner, strict)
+ return false unless self_restriction.restriction_of?(other_restriction, owner)
end
end
@@ -182,7 +182,7 @@ module Crystal
# If both double splat have restrictions, check which one is stricter
if self_double_splat_restriction && other_double_splat_restriction
- return false unless self_double_splat_restriction.restriction_of?(other_double_splat_restriction, owner, strict)
+ return false unless self_double_splat_restriction.restriction_of?(other_double_splat_restriction, owner)
elsif self_double_splat_restriction
# If only self has a restriction, it's stricter than the other
return true
@@ -241,14 +241,14 @@ module Crystal
end
class Path
- def restriction_of?(other : Path, owner, strict = false)
+ def restriction_of?(other : Path, owner)
return true if self == other
self_type = owner.lookup_path(self)
if self_type
other_type = owner.lookup_path(other)
if other_type
- return self_type.restriction_of?(other_type, owner, strict)
+ return self_type.restriction_of?(other_type, owner)
else
return true
end
@@ -257,17 +257,17 @@ module Crystal
false
end
- def restriction_of?(other : Union, owner, strict = false)
+ def restriction_of?(other : Union, owner)
# `true` if this type is a restriction of any type in the union
- other.types.any? { |o| self.restriction_of?(o, owner, strict) }
+ other.types.any? { |o| self.restriction_of?(o, owner) }
end
- def restriction_of?(other : Generic, owner, strict = false)
+ def restriction_of?(other : Generic, owner)
self_type = owner.lookup_path(self)
if self_type
other_type = owner.lookup_type?(other)
if other_type
- return self_type.restriction_of?(other_type, owner, strict)
+ return self_type.restriction_of?(other_type, owner)
end
end
@@ -281,23 +281,23 @@ module Crystal
false
end
- def restriction_of?(other, owner, strict = false)
+ def restriction_of?(other, owner)
false
end
end
class Union
- def restriction_of?(other : Path, owner, strict = false)
+ def restriction_of?(other : Path, owner)
# For a union to be considered before a path,
# all types in the union must be considered before
# that path.
# For example when using all subtypes of a parent type.
- types.all? &.restriction_of?(other, owner, strict)
+ types.all? &.restriction_of?(other, owner)
end
end
class Generic
- def restriction_of?(other : Path, owner, strict = false)
+ def restriction_of?(other : Path, owner)
# ```
# def foo(param : Array(T)) forall T
# end
@@ -312,7 +312,7 @@ module Crystal
if self_type
other_type = owner.lookup_path(other)
if other_type
- return self_type.restriction_of?(other_type, owner, strict)
+ return self_type.restriction_of?(other_type, owner)
end
end
@@ -332,19 +332,19 @@ module Crystal
true
end
- def restriction_of?(other : Generic, owner, strict = false)
+ def restriction_of?(other : Generic, owner)
return true if self == other
return false unless name == other.name && type_vars.size == other.type_vars.size
# Special case: NamedTuple against NamedTuple
if (self_type = owner.lookup_type?(self)).is_a?(NamedTupleInstanceType)
if (other_type = owner.lookup_type?(other)).is_a?(NamedTupleInstanceType)
- return self_type.restriction_of?(other_type, owner, strict)
+ return self_type.restriction_of?(other_type, owner)
end
end
type_vars.zip(other.type_vars) do |type_var, other_type_var|
- return false unless type_var.restriction_of?(other_type_var, owner, strict)
+ return false unless type_var.restriction_of?(other_type_var, owner)
end
true
@@ -352,7 +352,7 @@ module Crystal
end
class GenericClassType
- def restriction_of?(other : GenericClassInstanceType, owner, strict = false)
+ def restriction_of?(other : GenericClassInstanceType, owner)
# ```
# def foo(param : Array)
# end
@@ -374,7 +374,7 @@ module Crystal
end
class GenericClassInstanceType
- def restriction_of?(other : GenericClassType, owner, strict = false)
+ def restriction_of?(other : GenericClassType, owner)
# ```
# def foo(param : Array(Int32))
# end
@@ -392,11 +392,11 @@ module Crystal
end
class Metaclass
- def restriction_of?(other : Metaclass, owner, strict = false)
- name.restriction_of?(other.name, owner, strict)
+ def restriction_of?(other : Metaclass, owner)
+ name.restriction_of?(other.name, owner)
end
- def restriction_of?(other : Path, owner, strict = false)
+ def restriction_of?(other : Path, owner)
other_type = owner.lookup_type(other)
# Special case: when comparing Foo.class to Class, Foo.class has precedence
@@ -423,7 +423,7 @@ module Crystal
return self
end
- if parents.try &.any? &.restriction_of?(other, context.instantiated_type, context.strict?)
+ if parents.try &.any? &.restriction_of?(other, context.instantiated_type)
return self
end
@@ -467,7 +467,7 @@ module Crystal
def restrict(other : GenericClassType, context)
parents.try &.each do |parent|
if parent.module?
- return self if parent.restriction_of?(other, context.instantiated_type, context.strict?)
+ return self if parent.restriction_of?(other, context.instantiated_type)
else
restricted = parent.restrict other, context
return self if restricted
@@ -611,31 +611,31 @@ module Crystal
raise "BUG: unsupported restriction: #{self} vs. #{other}"
end
- def restriction_of?(other : UnionType, owner, strict = false)
- other.union_types.any? { |subtype| restriction_of?(subtype, owner, strict) }
+ def restriction_of?(other : UnionType, owner)
+ other.union_types.any? { |subtype| restriction_of?(subtype, owner) }
end
- def restriction_of?(other : VirtualType, owner, strict = false)
+ def restriction_of?(other : VirtualType, owner)
implements? other.base_type
end
- def restriction_of?(other : Type, owner, strict = false)
+ def restriction_of?(other : Type, owner)
if self == other
return true
end
- parents.try &.any? &.restriction_of?(other, owner, strict)
+ parents.try &.any? &.restriction_of?(other, owner)
end
- def restriction_of?(other : AliasType, owner, strict = false)
+ def restriction_of?(other : AliasType, owner)
if self == other
true
else
- restriction_of?(other.remove_alias, owner, strict)
+ restriction_of?(other.remove_alias, owner)
end
end
- def restriction_of?(other : ASTNode, owner, strict = false)
+ def restriction_of?(other : ASTNode, owner)
raise "BUG: called #{self}.restriction_of?(#{other})"
end
@@ -645,8 +645,8 @@ module Crystal
end
class UnionType
- def restriction_of?(type, owner, strict = false)
- self == type || union_types.all? &.restriction_of?(type, owner, strict)
+ def restriction_of?(type, owner)
+ self == type || union_types.all? &.restriction_of?(type, owner)
end
def restrict(other : Union, context)
@@ -719,23 +719,22 @@ module Crystal
end
class GenericInstanceType
- def restriction_of?(other : GenericType, owner, strict = false)
+ def restriction_of?(other : GenericType, owner)
return true if generic_type == other
super
end
- def restriction_of?(other : GenericInstanceType, owner, strict = false)
+ def restriction_of?(other : GenericInstanceType, owner)
return super unless generic_type == other.generic_type
type_vars.each do |name, type_var|
other_type_var = other.type_vars[name]
if type_var.is_a?(Var) && other_type_var.is_a?(Var)
- restricted = if strict
- type_var.type.devirtualize == other_type_var.type.devirtualize
- else
- type_var.type.implements?(other_type_var.type)
- end
- return nil unless restricted
+ # This overload can be called when the restriction node has a type due
+ # to e.g. AbstractDefChecker; generic instances shall behave like AST
+ # nodes when def restrictions are considered, i.e. all generic type
+ # variables are covariant.
+ return nil unless type_var.type.implements?(other_type_var.type)
else
return nil unless type_var == other_type_var
end
@@ -749,7 +748,7 @@ module Crystal
parents.try &.each do |parent|
if parent.module?
- return self if parent.restriction_of?(other, context.instantiated_type, context.strict?)
+ return self if parent.restriction_of?(other, context.instantiated_type)
else
restricted = parent.restrict other, context
return self if restricted
@@ -897,9 +896,7 @@ module Crystal
end
if type_var.is_a?(ASTNode)
- type_var.restriction_of?(other_type_var, context.instantiated_type, context.strict?)
- elsif context.strict?
- type_var == other_type_var
+ type_var.restriction_of?(other_type_var, context.instantiated_type)
else
# To prevent infinite recursion, it checks equality between
# `type_var` and `other_type_var` directly before try to restrict
@@ -910,7 +907,7 @@ module Crystal
end
class TupleInstanceType
- def restriction_of?(other : TupleInstanceType, owner, strict = false)
+ def restriction_of?(other : TupleInstanceType, owner)
return true if self == other || self.implements?(other)
false
@@ -970,7 +967,7 @@ module Crystal
end
class NamedTupleInstanceType
- def restriction_of?(other : NamedTupleInstanceType, owner, strict = false)
+ def restriction_of?(other : NamedTupleInstanceType, owner)
return true if self == other || self.implements?(other)
false
@@ -1009,7 +1006,7 @@ module Crystal
end
class VirtualType
- def restriction_of?(other : Type, owner, strict = false)
+ def restriction_of?(other : Type, owner)
other = other.base_type if other.is_a?(VirtualType)
base_type.implements?(other) || other.implements?(base_type)
end
@@ -1087,10 +1084,10 @@ module Crystal
end
class AliasType
- def restriction_of?(other, owner, strict = false)
+ def restriction_of?(other, owner)
return true if self == other
- remove_alias.restriction_of?(other, owner, strict)
+ remove_alias.restriction_of?(other, owner)
end
def restrict(other : Path, context)
@@ -1178,8 +1175,8 @@ module Crystal
restricted ? self : nil
end
- def restriction_of?(other : VirtualMetaclassType, owner, strict = false)
- restriction_of?(other.base_type.metaclass, owner, strict)
+ def restriction_of?(other : VirtualMetaclassType, owner)
+ restriction_of?(other.base_type.metaclass, owner)
end
end
diff --git a/src/compiler/crystal/semantic/type_declaration_processor.cr b/src/compiler/crystal/semantic/type_declaration_processor.cr
index 98ac0b43a573..b5770704238e 100644
--- a/src/compiler/crystal/semantic/type_declaration_processor.cr
+++ b/src/compiler/crystal/semantic/type_declaration_processor.cr
@@ -118,6 +118,10 @@ struct Crystal::TypeDeclarationProcessor
# Types whose initialize methods are all macro defs
@has_macro_def = Set(Type).new
+ # Types that are not extended by any other types, used to speed up detection
+ # of instance vars in extended modules
+ @has_no_extenders = Set(Type).new
+
@type_decl_visitor = TypeDeclarationVisitor.new(@program, @explicit_instance_vars)
@type_guess_visitor = TypeGuessVisitor.new(@program, @explicit_instance_vars,
@@ -254,7 +258,11 @@ struct Crystal::TypeDeclarationProcessor
# set from uninstantiated generic types
return if owner.is_a?(GenericInstanceType)
- if owner.metaclass?
+ if owner.is_a?(NonGenericModuleType) || owner.is_a?(GenericModuleType)
+ if extender = find_extending_type(owner)
+ raise TypeException.new("can't declare instance variables in #{owner} because #{extender} extends it", type_decl.location)
+ end
+ elsif owner.metaclass?
raise TypeException.new("can't declare instance variables in #{owner}", type_decl.location)
end
@@ -299,6 +307,26 @@ struct Crystal::TypeDeclarationProcessor
end
end
+ private def find_extending_type(mod)
+ return nil if @has_no_extenders.includes?(mod)
+
+ mod.raw_including_types.try &.each do |includer|
+ case includer
+ when .metaclass?
+ return includer.instance_type
+ when NonGenericModuleType
+ type = find_extending_type(includer)
+ return type if type
+ when GenericModuleInstanceType
+ type = find_extending_type(includer.generic_type.as(GenericModuleType))
+ return type if type
+ end
+ end
+
+ @has_no_extenders << mod
+ nil
+ end
+
private def check_non_nilable_for_generic_module(owner, name, type_decl)
case owner
when GenericModuleType
@@ -344,6 +372,14 @@ struct Crystal::TypeDeclarationProcessor
# set from uninstantiated generic types
return if owner.is_a?(GenericInstanceType)
+ if owner.is_a?(NonGenericModuleType) || owner.is_a?(GenericModuleType)
+ if extender = find_extending_type(owner)
+ raise TypeException.new("can't declare instance variables in #{owner} because #{extender} extends it", type_info.location)
+ end
+ elsif owner.metaclass?
+ raise TypeException.new("can't declare instance variables in #{owner}", type_info.location)
+ end
+
# If a superclass already defines this variable we ignore
# the guessed type information for subclasses
supervar = owner.lookup_instance_var?(name)
diff --git a/src/compiler/crystal/semantic/type_intersect.cr b/src/compiler/crystal/semantic/type_intersect.cr
new file mode 100644
index 000000000000..c05ba59bf08f
--- /dev/null
+++ b/src/compiler/crystal/semantic/type_intersect.cr
@@ -0,0 +1,268 @@
+require "../program"
+
+module Crystal
+ class Type
+ # Given two types T and U, returns a common descendent V such that V <= T
+ # and V <= U. This is the same as:
+ #
+ # ```
+ # typeof(begin
+ # x = uninitialized T
+ # x.is_a?(U) ? x : raise ""
+ # end)
+ # ```
+ #
+ # except that `nil` is returned if the above produces `NoReturn`.
+ def self.common_descendent(type1 : Type, type2 : Type)
+ common_descendent_base(type1, type2)
+ end
+
+ def self.common_descendent(type1 : TupleInstanceType, type2 : TupleInstanceType)
+ type1.implements?(type2) ? type1 : nil
+ end
+
+ def self.common_descendent(type1 : NamedTupleInstanceType, type2 : NamedTupleInstanceType)
+ type1.implements?(type2) ? type1 : nil
+ end
+
+ def self.common_descendent(type1 : ProcInstanceType, type2 : ProcInstanceType)
+ type1.compatible_with?(type2) ? type2 : nil
+ end
+
+ def self.common_descendent(type1 : NonGenericModuleType | GenericModuleInstanceType, type2 : AliasType)
+ common_descendent(type1, type2.remove_alias) ||
+ common_descendent_including_types(type1, type2)
+ end
+
+ def self.common_descendent(type1 : NonGenericModuleType | GenericModuleInstanceType, type2 : UnionType)
+ common_descendent_union(type1, type2) ||
+ common_descendent_including_types(type1, type2)
+ end
+
+ def self.common_descendent(type1 : NonGenericModuleType | GenericModuleInstanceType, type2 : VirtualType)
+ common_descendent_including_types(type1, type2)
+ end
+
+ def self.common_descendent(type1 : NonGenericModuleType | GenericModuleInstanceType, type2 : GenericClassType)
+ common_descendent_instance_and_generic(type1, type2) ||
+ common_descendent_including_types(type1, type2)
+ end
+
+ def self.common_descendent(type1 : GenericModuleInstanceType, type2 : GenericModuleInstanceType)
+ common_descendent_generic_instances(type1, type2) ||
+ common_descendent_base(type1, type2) ||
+ common_descendent_including_types(type1, type2)
+ end
+
+ def self.common_descendent(type1 : GenericModuleInstanceType, type2 : GenericModuleType)
+ return type1 if type1.generic_type == type2
+
+ common_descendent_instance_and_generic(type1, type2) ||
+ common_descendent_including_types(type1, type2)
+ end
+
+ def self.common_descendent(type1 : NonGenericModuleType | GenericModuleInstanceType, type2 : Type)
+ common_descendent_base(type1, type2) ||
+ common_descendent_including_types(type1, type2)
+ end
+
+ def self.common_descendent(type1 : GenericClassInstanceType, type2 : GenericClassType)
+ return type1 if type1.generic_type == type2
+
+ common_descendent_instance_and_generic(type1, type2)
+ end
+
+ def self.common_descendent(type1 : GenericInstanceType, type2 : GenericInstanceType)
+ common_descendent_generic_instances(type1, type2) ||
+ common_descendent_base(type1, type2)
+ end
+
+ def self.common_descendent(type1 : MetaclassType, type2 : VirtualMetaclassType)
+ # A module class can't be restricted into a class
+ return nil if type1.instance_type.module?
+
+ restricted = common_descendent(type1.instance_type, type2.instance_type.base_type)
+ restricted ? type1 : nil
+ end
+
+ def self.common_descendent(type1 : GenericClassInstanceMetaclassType | GenericModuleInstanceMetaclassType, type2 : MetaclassType)
+ return type1 if type1.instance_type.generic_type.metaclass == type2
+
+ restricted = common_descendent(type1.instance_type, type2.instance_type)
+ restricted ? type1 : nil
+ end
+
+ def self.common_descendent(type1 : UnionType, type2 : Type)
+ types = type1.union_types.compact_map do |union_type|
+ common_descendent(union_type, type2)
+ end
+ type1.program.type_merge_union_of(types)
+ end
+
+ def self.common_descendent(type1 : AliasType, type2 : AliasType)
+ return type1 if type1 == type2
+
+ if !type1.simple? && !type2.simple?
+ return nil
+ end
+
+ common_descendent(type1.remove_alias, type2)
+ end
+
+ def self.common_descendent(type1 : AliasType, type2 : Type)
+ common_descendent(type1.remove_alias, type2)
+ end
+
+ def self.common_descendent(type1 : TypeDefType, type2 : UnionType)
+ common_descendent_union(type1, type2)
+ end
+
+ def self.common_descendent(type1 : TypeDefType, type2 : AliasType)
+ type2 = type2.remove_alias
+ return type1 if type1 == type2
+ common_descendent(type1, type2)
+ end
+
+ def self.common_descendent(type1 : TypeDefType, type2 : Type)
+ return type1 if type1 == type2
+
+ restricted = common_descendent(type1.typedef, type2)
+ if restricted == type1.typedef
+ return type1
+ elsif restricted.is_a?(UnionType)
+ type1.program.type_merge(restricted.union_types.map { |t| t == type1.typedef ? type1 : t })
+ else
+ restricted
+ end
+ end
+
+ def self.common_descendent(type1 : VirtualType, type2 : VirtualType)
+ return type1 if type1 == type2
+
+ base_type1 = type1.base_type
+ base_type2 = type2.base_type
+ (common_descendent(base_type1, base_type2) || common_descendent(base_type2, base_type1)).try &.virtual_type
+ end
+
+ def self.common_descendent(type1 : VirtualType, type2 : AliasType)
+ common_descendent(type1, type2.remove_alias)
+ end
+
+ def self.common_descendent(type1 : VirtualType, type2 : UnionType)
+ types = type2.union_types.compact_map do |t|
+ common_descendent(type1, t)
+ end
+ type1.program.type_merge types
+ end
+
+ def self.common_descendent(type1 : VirtualType, type2 : Type)
+ base_type = type1.base_type
+
+ if type2.implements?(base_type)
+ type2.virtual_type
+ elsif base_type.implements?(type2)
+ type1
+ elsif type2.module?
+ types = base_type.subclasses.compact_map do |subclass|
+ common_descendent(subclass.virtual_type, type2)
+ end
+ type1.program.type_merge_union_of types
+ elsif base_type.is_a?(GenericInstanceType) && type2.is_a?(GenericType)
+ # Consider the case of Foo(Int32) vs. Bar(T), with Bar(T) < Foo(T):
+ # we want to return Bar(Int32), so we search in Bar's generic instantiations
+ types = type2.instantiated_types.compact_map do |instance|
+ next if instance.unbound? || instance.abstract?
+ instance.virtual_type if instance.implements?(base_type)
+ end
+ type1.program.type_merge_union_of types
+ else
+ nil
+ end
+ end
+
+ def self.common_descendent(type1 : NilType, type2 : VoidType)
+ # Allow Nil to match Void (useful for `Pointer(Void)#value=`)
+ type1
+ end
+
+ def self.common_descendent(type1 : GenericClassType, type2 : GenericClassType)
+ return type1 if type1 == type2
+
+ common_descendent_instance_and_generic(type1, type2)
+ end
+
+ def self.common_descendent(type1 : Type, type2 : AliasType)
+ return type1 if type1 == type2
+
+ common_descendent(type1, type2.remove_alias)
+ end
+
+ def self.common_descendent(type1 : Type, type2 : UnionType)
+ common_descendent_union(type1, type2)
+ end
+
+ def self.common_descendent(type1 : Type, type2 : VirtualType)
+ type1.implements?(type2.base_type) ? type1 : nil
+ end
+
+ def self.common_descendent(type1 : Type, type2 : GenericClassType)
+ common_descendent_instance_and_generic(type1, type2)
+ end
+
+ private def self.common_descendent_base(type1, type2)
+ if type1 == type2
+ return type1
+ end
+
+ if type1.parents.try &.any? &.implements?(type2)
+ return type1
+ end
+ end
+
+ private def self.common_descendent_union(type, union)
+ restricted = nil
+
+ union.union_types.each do |union_type|
+ # Apply the restriction logic on each union type, even if we already
+ # have a match, so that we can detect ambiguous calls between of
+ # literal types against aliases that resolve to union types.
+ restriction = common_descendent(type, union_type)
+ restricted ||= restriction
+ end
+
+ restricted ? type : nil
+ end
+
+ private def self.common_descendent_including_types(mod, type)
+ mod.including_types.try { |t| common_descendent(t, type) }
+ end
+
+ private def self.common_descendent_instance_and_generic(instance, generic)
+ instance.parents.try &.each do |parent|
+ if parent.module?
+ return instance if parent.implements?(generic)
+ else
+ restricted = common_descendent(parent, generic)
+ return instance if restricted
+ end
+ end
+ end
+
+ private def self.common_descendent_generic_instances(type1, type2)
+ return nil unless type1.generic_type == type2.generic_type
+
+ type1.type_vars.each do |name, type_var1|
+ type_var2 = type2.type_vars[name]
+ if type_var1.is_a?(Var) && type_var2.is_a?(Var)
+ # type vars are invariant except for Tuple and NamedTuple and those have
+ # separate logic
+ return nil unless type_var1.type.devirtualize == type_var2.type.devirtualize
+ else
+ return nil unless type_var1 == type_var2
+ end
+ end
+
+ type1
+ end
+ end
+end
diff --git a/src/compiler/crystal/syntax/lexer.cr b/src/compiler/crystal/syntax/lexer.cr
index 570a260e9806..bc2614a26ad9 100644
--- a/src/compiler/crystal/syntax/lexer.cr
+++ b/src/compiler/crystal/syntax/lexer.cr
@@ -55,6 +55,7 @@ module Crystal
def initialize(string, string_pool : StringPool? = nil)
@reader = Char::Reader.new(string)
@token = Token.new
+ @temp_token = Token.new
@line_number = 1
@column_number = 1
@filename = ""
@@ -636,6 +637,8 @@ module Crystal
when '~'
next_char :"~"
when '.'
+ line = @line_number
+ column = @column_number
case next_char
when '.'
case next_char
@@ -644,6 +647,8 @@ module Crystal
else
@token.type = :".."
end
+ when .ascii_number?
+ raise ".1 style number literal is not supported, put 0 before dot", line, column
else
@token.type = :"."
end
@@ -2411,7 +2416,10 @@ module Crystal
when '\\'
char = next_char
if delimiter_state
- if char == delimiter_state.end
+ case char
+ when delimiter_state.end
+ char = next_char
+ when '\\'
char = next_char
end
whitespace = false
@@ -2524,14 +2532,23 @@ module Crystal
@token
end
- def lookahead
- old_pos = @reader.pos
- old_line_number, old_column_number = @line_number, @column_number
+ def lookahead(preserve_token_on_fail = false)
+ old_pos, old_line, old_column = current_pos, @line_number, @column_number
+ @temp_token.copy_from(@token) if preserve_token_on_fail
result = yield
unless result
- @reader.pos = old_pos
- @line_number, @column_number = old_line_number, old_column_number
+ self.current_pos, @line_number, @column_number = old_pos, old_line, old_column
+ @token.copy_from(@temp_token) if preserve_token_on_fail
+ end
+ result
+ end
+
+ def peek_ahead
+ result = uninitialized typeof(yield)
+ lookahead(preserve_token_on_fail: true) do
+ result = yield
+ nil
end
result
end
diff --git a/src/compiler/crystal/syntax/parser.cr b/src/compiler/crystal/syntax/parser.cr
index 2eccad8ae456..f63b28f993bd 100644
--- a/src/compiler/crystal/syntax/parser.cr
+++ b/src/compiler/crystal/syntax/parser.cr
@@ -26,7 +26,6 @@ module Crystal
def initialize(str, string_pool : StringPool? = nil, @def_vars = [Set(String).new])
super(str, string_pool)
- @temp_token = Token.new
@unclosed_stack = [] of Unclosed
@calls_super = false
@calls_initialize = false
@@ -624,12 +623,7 @@ module Crystal
end
# Allow '.' after newline for chaining calls
- old_pos, old_line, old_column = current_pos, @line_number, @column_number
- @temp_token.copy_from @token
- next_token_skip_space_or_newline
- unless @token.type == :"."
- self.current_pos, @line_number, @column_number = old_pos, old_line, old_column
- @token.copy_from @temp_token
+ unless lookahead(preserve_token_on_fail: true) { next_token_skip_space_or_newline; @token.type == :"." }
break
end
when :"."
@@ -965,21 +959,10 @@ module Crystal
location = @token.location
var = Var.new(@token.to_s).at(location)
- old_pos, old_line, old_column = current_pos, @line_number, @column_number
- @temp_token.copy_from(@token)
-
- next_token_skip_space
-
- if @token.type == :"="
- @token.copy_from(@temp_token)
- self.current_pos, @line_number, @column_number = old_pos, old_line, old_column
-
+ if peek_ahead { next_token_skip_space; @token.type == :"=" }
push_var var
node_and_next_token var
else
- @token.copy_from(@temp_token)
- self.current_pos, @line_number, @column_number = old_pos, old_line, old_column
-
node_and_next_token Global.new(var.name).at(location)
end
when :GLOBAL_MATCH_DATA_INDEX
@@ -5085,20 +5068,18 @@ module Crystal
# Looks ahead next tokens to check whether they indicate type.
def type_start?(*, consume_newlines)
- old_pos, old_line, old_column = current_pos, @line_number, @column_number
- @temp_token.copy_from(@token)
+ peek_ahead do
+ begin
+ if consume_newlines
+ next_token_skip_space_or_newline
+ else
+ next_token_skip_space
+ end
- begin
- if consume_newlines
- next_token_skip_space_or_newline
- else
- next_token_skip_space
+ type_start?
+ rescue
+ false
end
-
- type_start?
- ensure
- @token.copy_from(@temp_token)
- self.current_pos, @line_number, @column_number = old_pos, old_line, old_column
end
end
@@ -5368,7 +5349,7 @@ module Crystal
args = call_args.args if call_args
if args && !args.empty?
- if args.size == 1
+ if args.size == 1 && !args.first.is_a?(Splat)
node = klass.new(args.first)
else
tuple = TupleLiteral.new(args).at(args.last)
diff --git a/src/compiler/crystal/tools/doc/generator.cr b/src/compiler/crystal/tools/doc/generator.cr
index 6297e23665e5..b5c2b4eb2dcc 100644
--- a/src/compiler/crystal/tools/doc/generator.cr
+++ b/src/compiler/crystal/tools/doc/generator.cr
@@ -1,3 +1,5 @@
+require "../../../../../lib/markd/src/markd"
+
class Crystal::Doc::Generator
getter program : Program
@@ -310,12 +312,17 @@ class Crystal::Doc::Generator
def doc(context, string)
string = isolate_flag_lines string
string += build_flag_lines_from_annotations context
- markdown = String.build do |io|
- Markdown.parse string, Markdown::DocRenderer.new(context, io)
- end
+ markdown = render_markdown(context, string)
generate_flags markdown
end
+ private def render_markdown(context, source)
+ options = ::Markd::Options.new
+ document = ::Markd::Parser.parse(source, options)
+ renderer = MarkdDocRenderer.new(context, options)
+ renderer.render(document).chomp
+ end
+
def fetch_doc_lines(doc : String) : String
doc.gsub /\n+/ { |match| match.size == 1 ? " " : "\n" }
end
diff --git a/src/compiler/crystal/tools/doc/markdown/doc_renderer.cr b/src/compiler/crystal/tools/doc/markd_doc_renderer.cr
similarity index 51%
rename from src/compiler/crystal/tools/doc/markdown/doc_renderer.cr
rename to src/compiler/crystal/tools/doc/markd_doc_renderer.cr
index 2fb2d7e62009..76c5ba33aab6 100644
--- a/src/compiler/crystal/tools/doc/markdown/doc_renderer.cr
+++ b/src/compiler/crystal/tools/doc/markd_doc_renderer.cr
@@ -1,37 +1,57 @@
-require "./*"
-
-class Crystal::Doc::Markdown::DocRenderer < Crystal::Doc::Markdown::HTMLRenderer
- def self.new(obj : Constant | Macro | Method, io)
- new obj.type, io
+class Crystal::Doc::MarkdDocRenderer < Markd::HTMLRenderer
+ def initialize(@type : Crystal::Doc::Type, options)
+ super(options)
end
- @type : Crystal::Doc::Type
+ def self.new(obj : Constant | Macro | Method, options)
+ new obj.type, options
+ end
- def initialize(@type : Crystal::Doc::Type, io)
- super(io)
+ def heading(node : Markd::Node, entering : Bool)
+ tag_name = HEADINGS[node.data["level"].as(Int32) - 1]
+ if entering
+ anchor = collect_text(node)
+ .underscore # Underscore the string
+ .gsub(/[^\w\d\s\-.~]/, "") # Delete unsafe URL characters
+ .strip # Strip leading/trailing whitespace
+ .gsub(/[\s_-]+/, '-') # Replace `_` and leftover whitespace with `-`
+
+ tag(tag_name, attrs(node))
+ literal Crystal::Doc.anchor_link(anchor)
+ else
+ tag(tag_name, end_tag: true)
+ newline
+ end
+ end
- @inside_inline_code = false
- @code_buffer = IO::Memory.new
- @inside_code = false
- @inside_link = false
+ def collect_text(main)
+ String.build do |io|
+ walker = main.walker
+ while item = walker.next
+ node, entering = item
+ if entering && (text = node.text)
+ io << text
+ end
+ end
+ end
end
- # For inline code we search if there's a method with that name in
- # the current type (it's usual to refer to these as `method`).
- #
- # If there is a match, we output the link without the ...
- # tag (looks better). If there isn't a match, we want to preserve the code tag.
- def begin_inline_code
- super
- @inside_inline_code = true
- @code_buffer.clear
+ def code(node : Markd::Node, entering : Bool)
+ tag("code") do
+ if in_link?(node)
+ output(node.text)
+ else
+ literal(expand_code_links(node.text))
+ end
+ end
end
- def end_inline_code
- @inside_inline_code = false
+ def in_link?(node)
+ parent = node.parent?
+ return false unless parent
+ return true if parent.type.link?
- @io << expand_code_links(@code_buffer.to_s)
- super
+ in_link?(parent)
end
def expand_code_links(text : String) : String
@@ -87,71 +107,50 @@ class Crystal::Doc::Markdown::DocRenderer < Crystal::Doc::Markdown::HTMLRenderer
end
end
- def begin_code(language = nil)
- if language.nil? || language == "cr"
- language = "crystal"
- end
-
- super
-
- if language == "crystal"
- @inside_code = true
- @code_buffer.clear
- end
- end
-
- def end_code
- if @inside_code
- text = Highlighter.highlight(@code_buffer.to_s)
- @io << text
- end
-
- @inside_code = false
-
- super
- end
-
- def begin_link(url)
- @io << %()
+ def code_block(node : Markd::Node, entering : Bool)
+ languages = node.fence_language ? node.fence_language.split : nil
+ code_tag_attrs = attrs(node)
+ pre_tag_attrs = if @options.prettyprint
+ {"class" => "prettyprint"}
+ else
+ nil
+ end
- @inside_link = true
- end
-
- def end_link
- super
- @inside_link = false
- end
+ language = languages.try &.first?.try &.strip
+ language = nil if language.try &.empty?
- def text(text)
- if @inside_code
- @code_buffer << text
- return
+ if language.nil? || language == "cr"
+ language = "crystal"
end
- if @inside_link
- super
- return
+ if language
+ code_tag_attrs ||= {} of String => String
+ code_tag_attrs["class"] = "language-#{escape(language)}"
end
- if @inside_inline_code
- @code_buffer << text
- return
+ newline
+ tag("pre", pre_tag_attrs) do
+ tag("code", code_tag_attrs) do
+ code = node.text.chomp
+ if language == "crystal"
+ literal(Highlighter.highlight code)
+ else
+ output(code)
+ end
+ end
end
-
- super(text)
+ newline
end
- def type_link(type, text)
+ private def type_link(type, text)
%(#{text})
end
- def method_link(method, text)
+ private def method_link(method, text)
%(#{text})
end
- def lookup_method(type, name, args, kind = nil)
+ private def lookup_method(type, name, args, kind = nil)
case args
when ""
args_count = nil
diff --git a/src/compiler/crystal/tools/doc/markdown/html_renderer.cr b/src/compiler/crystal/tools/doc/markdown/html_renderer.cr
deleted file mode 100644
index 3e6880bcc597..000000000000
--- a/src/compiler/crystal/tools/doc/markdown/html_renderer.cr
+++ /dev/null
@@ -1,121 +0,0 @@
-require "./renderer"
-
-class Crystal::Doc::Markdown::HTMLRenderer
- include Renderer
-
- def initialize(@io : IO)
- end
-
- def begin_paragraph
- @io << ""
- end
-
- def end_paragraph
- @io << "
"
- end
-
- def begin_italic
- @io << ""
- end
-
- def end_italic
- @io << ""
- end
-
- def begin_bold
- @io << ""
- end
-
- def end_bold
- @io << ""
- end
-
- def begin_header(level : Int32, anchor : String) : Nil
- @io << "'
- @io << Crystal::Doc.anchor_link(anchor)
- end
-
- def end_header(level)
- @io << "'
- end
-
- def begin_inline_code
- @io << ""
- end
-
- def end_inline_code
- @io << "
"
- end
-
- def begin_code(language)
- if language.nil?
- @io << ""
- else
- @io << %()
- end
- end
-
- def end_code
- @io << "
"
- end
-
- def begin_quote
- @io << ""
- end
-
- def end_quote
- @io << "
"
- end
-
- def begin_unordered_list
- @io << ""
- end
-
- def end_unordered_list
- @io << "
"
- end
-
- def begin_ordered_list
- @io << ""
- end
-
- def end_ordered_list
- @io << "
"
- end
-
- def begin_list_item
- @io << ""
- end
-
- def end_list_item
- @io << ""
- end
-
- def begin_link(url)
- @io << %()
- end
-
- def end_link
- @io << ""
- end
-
- def image(url, alt)
- @io << %()
- end
-
- def text(text)
- @io << text.gsub('<', "<")
- end
-
- def horizontal_rule
- @io << "
"
- end
-end
diff --git a/src/compiler/crystal/tools/doc/markdown/markdown.cr b/src/compiler/crystal/tools/doc/markdown/markdown.cr
deleted file mode 100644
index 90c595010168..000000000000
--- a/src/compiler/crystal/tools/doc/markdown/markdown.cr
+++ /dev/null
@@ -1,36 +0,0 @@
-# Basic implementation of Markdown for the `crystal doc` tool.
-#
-# It lacks many features and it has some bugs too. Eventually we should replace
-# it with something more feature-complete (like https://github.com/icyleaf/markd)
-# but that means the compiler will start depending on external shards. Otherwise
-# we should extract the doc as a separate tool/binary.
-# We don't expose this library in the standard library because it's probable
-# that we will never make it feature complete.
-#
-# Usage:
-#
-# ```
-# require "compiler/crystal/tools/doc/markdown"
-#
-# text = "## This is title \n This is a [link](https://crystal-lang.org)"
-#
-# Crystal::Doc::Markdown.to_html(text)
-# # => This is title
-# # => This is a link
-# ```
-module Crystal::Doc::Markdown
- def self.parse(text, renderer)
- parser = Parser.new(text, renderer)
- parser.parse
- end
-
- def self.to_html(text) : String
- String.build do |io|
- parse text, Markdown::HTMLRenderer.new(io)
- end
- end
-end
-
-require "./parser"
-require "./renderer"
-require "./html_renderer"
diff --git a/src/compiler/crystal/tools/doc/markdown/parser.cr b/src/compiler/crystal/tools/doc/markdown/parser.cr
deleted file mode 100644
index d6fc1509aadb..000000000000
--- a/src/compiler/crystal/tools/doc/markdown/parser.cr
+++ /dev/null
@@ -1,642 +0,0 @@
-class Crystal::Doc::Markdown::Parser
- record PrefixHeader, count : Int32
- record UnorderedList, char : Char
- record CodeFence, language : String
-
- @lines : Array(String)
-
- def initialize(text : String, @renderer : Renderer)
- @lines = text.lines
- @line = 0
- end
-
- def parse
- while @line < @lines.size
- process_paragraph
- end
- end
-
- def process_paragraph
- line = @lines[@line]
-
- case item = classify(line)
- when :empty
- @line += 1
- when :header1
- render_header 1, line, 2
- when :header2
- render_header 2, line, 2
- when PrefixHeader
- render_prefix_header(item.count, line)
- when :code
- render_code
- when :horizontal_rule
- render_horizontal_rule
- when UnorderedList
- render_unordered_list(item.char)
- when CodeFence
- render_fenced_code(item.language)
- when :ordered_list
- render_ordered_list
- when :quote
- render_quote
- else
- render_paragraph
- end
- end
-
- def classify(line)
- if empty? line
- return :empty
- end
-
- if pounds = count_pounds line
- return PrefixHeader.new(pounds)
- end
-
- if line.starts_with? " "
- return :code
- end
-
- if horizontal_rule? line
- return :horizontal_rule
- end
-
- if starts_with_bullet_list_marker?(line, '*')
- return UnorderedList.new('*')
- end
-
- if starts_with_bullet_list_marker?(line, '+')
- return UnorderedList.new('+')
- end
-
- if starts_with_bullet_list_marker?(line, '-')
- return UnorderedList.new('-')
- end
-
- if (code_fence = code_fence?(line))
- return code_fence
- end
-
- if starts_with_digits_dot? line
- return :ordered_list
- end
-
- if line.starts_with? ">"
- return :quote
- end
-
- if next_line_is_all?('=')
- return :header1
- end
-
- if next_line_is_all?('-')
- return :header2
- end
-
- nil
- end
-
- def render_prefix_header(level, line)
- bytesize = line.bytesize
- str = line.to_unsafe
- pos = level
- while pos < bytesize && str[pos].unsafe_chr.ascii_whitespace?
- pos += 1
- end
-
- render_header level, line.byte_slice(pos), 1
- end
-
- def render_header(level : Int32, line : String, increment : Int32)
- anchor = line
- .underscore # Underscore the string
- .gsub(/[^\w\d\s\-.~]/, "") # Delete unsafe URL characters
- .strip # Strip leading/trailing whitespace
- .gsub(/[\s_-]+/, '-') # Replace `_` and leftover whitespace with `-`
-
- @renderer.begin_header level, anchor
- process_line line
- @renderer.end_header level
- @line += increment
-
- append_double_newline_if_has_more
- end
-
- def render_paragraph
- @renderer.begin_paragraph
-
- join_next_lines continue_on: nil
- process_line @lines[@line]
- @line += 1
-
- @renderer.end_paragraph
-
- append_double_newline_if_has_more
- end
-
- def render_code
- @renderer.begin_code nil
-
- while true
- line = @lines[@line]
-
- break unless has_code_spaces? line
-
- @renderer.text line.byte_slice(Math.min(line.bytesize, 4))
- @line += 1
-
- if @line == @lines.size
- break
- end
-
- if next_lines_empty_of_code?
- break
- end
-
- newline
- end
-
- @renderer.end_code
-
- append_double_newline_if_has_more
- end
-
- def render_fenced_code(language : String)
- line = @lines[@line]
-
- @renderer.begin_code language.presence
-
- @line += 1
-
- if @line < @lines.size
- while true
- line = @lines[@line]
-
- @renderer.text line
- @line += 1
-
- if (@line == @lines.size)
- break
- end
-
- if code_fence? @lines[@line]
- @line += 1
- break
- end
-
- newline
- end
- end
-
- @renderer.end_code
-
- append_double_newline_if_has_more
- end
-
- def render_quote
- @renderer.begin_quote
-
- join_next_lines continue_on: :quote
- line = @lines[@line]
-
- process_line line.byte_slice(line.index('>').not_nil! + 1)
-
- @line += 1
-
- @renderer.end_quote
-
- append_double_newline_if_has_more
- end
-
- def render_unordered_list(prefix = '*')
- @renderer.begin_unordered_list
-
- while true
- break unless starts_with_bullet_list_marker?(@lines[@line], prefix)
-
- join_next_lines continue_on: nil, stop_on: UnorderedList.new(prefix)
- line = @lines[@line]
-
- if empty? line
- @line += 1
-
- if @line == @lines.size
- break
- end
-
- next
- end
-
- if line.starts_with?(" ") && previous_line_is_not_intended_and_starts_with_bullet_list_marker?(prefix)
- @renderer.begin_unordered_list
- end
-
- @renderer.begin_list_item
- process_line line.byte_slice(line.index(prefix).not_nil! + 1)
- @renderer.end_list_item
-
- if line.starts_with?(" ") && next_line_is_not_intended?
- @renderer.end_unordered_list
- end
-
- @line += 1
-
- if @line == @lines.size
- break
- end
- end
-
- @renderer.end_unordered_list
-
- append_double_newline_if_has_more
- end
-
- def render_ordered_list
- @renderer.begin_ordered_list
-
- while true
- break unless starts_with_digits_dot? @lines[@line]
-
- join_next_lines continue_on: nil, stop_on: :ordered_list
- line = @lines[@line]
-
- if empty? line
- @line += 1
-
- if @line == @lines.size
- break
- end
-
- next
- end
-
- @renderer.begin_list_item
- process_line line.byte_slice(line.index('.').not_nil! + 1)
- @renderer.end_list_item
- @line += 1
-
- if @line == @lines.size
- break
- end
- end
-
- @renderer.end_ordered_list
-
- append_double_newline_if_has_more
- end
-
- def append_double_newline_if_has_more
- if @line < @lines.size
- newline
- newline
- end
- end
-
- def process_line(line)
- bytesize = line.bytesize
- str = line.to_unsafe
- pos = 0
-
- while pos < bytesize && str[pos].unsafe_chr.ascii_whitespace?
- pos += 1
- end
-
- cursor = pos
- one_star = false
- two_stars = false
- one_underscore = false
- two_underscores = false
- in_link = false
- last_is_space = true
-
- while pos < bytesize
- case str[pos].unsafe_chr
- when '*'
- if pos + 1 < bytesize && str[pos + 1].unsafe_chr == '*'
- if two_stars || has_closing?('*', 2, str, (pos + 2), bytesize)
- @renderer.text line.byte_slice(cursor, pos - cursor)
- pos += 1
- cursor = pos + 1
- if two_stars
- @renderer.end_bold
- else
- @renderer.begin_bold
- end
- two_stars = !two_stars
- end
- elsif one_star || has_closing?('*', 1, str, (pos + 1), bytesize)
- @renderer.text line.byte_slice(cursor, pos - cursor)
- cursor = pos + 1
- if one_star
- @renderer.end_italic
- else
- @renderer.begin_italic
- end
- one_star = !one_star
- end
- when '_'
- if pos + 1 < bytesize && str[pos + 1].unsafe_chr == '_'
- if two_underscores || (last_is_space && has_closing?('_', 2, str, (pos + 2), bytesize))
- @renderer.text line.byte_slice(cursor, pos - cursor)
- pos += 1
- cursor = pos + 1
- if two_underscores
- @renderer.end_bold
- else
- @renderer.begin_bold
- end
- two_underscores = !two_underscores
- end
- elsif one_underscore || (last_is_space && has_closing?('_', 1, str, (pos + 1), bytesize))
- @renderer.text line.byte_slice(cursor, pos - cursor)
- cursor = pos + 1
- if one_underscore
- @renderer.end_italic
- else
- @renderer.begin_italic
- end
- one_underscore = !one_underscore
- end
- when '`'
- if has_closing?('`', 1, str, (pos + 1), bytesize)
- @renderer.text line.byte_slice(cursor, pos - cursor)
- cursor = pos + 1
- @renderer.begin_inline_code
- idx = (str + pos + 1).to_slice(bytesize).index('`'.ord).not_nil!
- @renderer.text line.byte_slice(cursor, idx)
- pos = pos + 1 + idx
- @renderer.end_inline_code
- cursor = pos + 1
- end
- when '!'
- if pos + 1 < bytesize && str[pos + 1] === '['
- link = check_link str, (pos + 2), bytesize
- if link
- @renderer.text line.byte_slice(cursor, pos - cursor)
-
- bracket_idx = (str + pos + 2).to_slice(bytesize - pos - 2).index(']'.ord).not_nil!
- alt = line.byte_slice(pos + 2, bracket_idx)
-
- @renderer.image link, alt
-
- paren_idx = (str + pos + 2 + bracket_idx + 1).to_slice(bytesize - pos - 2 - bracket_idx - 1).index(')'.ord).not_nil!
- pos += 2 + bracket_idx + 1 + paren_idx
- cursor = pos + 1
- end
- end
- when '['
- unless in_link
- if link = check_link str, (pos + 1), bytesize
- @renderer.text line.byte_slice(cursor, pos - cursor)
- cursor = pos + 1
- @renderer.begin_link link
- in_link = true
- end
- end
- when ']'
- if in_link
- @renderer.text line.byte_slice(cursor, pos - cursor)
- @renderer.end_link
-
- paren_idx = (str + pos + 1).to_slice(bytesize - pos - 1).index(')'.ord).not_nil!
- pos += paren_idx + 1
- cursor = pos + 1
- in_link = false
- end
- end
- last_is_space = pos < bytesize && str[pos].unsafe_chr.ascii_whitespace?
- pos += 1
- end
-
- @renderer.text line.byte_slice(cursor, pos - cursor)
- end
-
- def empty?(line)
- line_is_all? line, ' '
- end
-
- def has_closing?(char, count, str, pos, bytesize)
- str += pos
- bytesize -= pos
- idx = str.to_slice(bytesize).index char.ord
- return false unless idx
-
- if count == 2
- return false unless idx + 1 < bytesize && str[idx + 1].unsafe_chr == char
- end
-
- !str[idx - 1].unsafe_chr.ascii_whitespace?
- end
-
- def check_link(str, pos, bytesize)
- # We need to count nested brackets to do it right
- bracket_count = 1
- while pos < bytesize
- case str[pos].unsafe_chr
- when '['
- bracket_count += 1
- when ']'
- bracket_count -= 1
- if bracket_count == 0
- break
- end
- end
- pos += 1
- end
-
- return nil unless bracket_count == 0
- bracket_idx = pos
-
- return nil unless str[bracket_idx + 1] === '('
-
- paren_idx = (str + bracket_idx + 1).to_slice(bytesize - bracket_idx - 1).index ')'.ord
- return nil unless paren_idx
-
- String.new(Slice.new(str + bracket_idx + 2, paren_idx - 1))
- end
-
- def next_line_is_all?(char)
- return false unless @line + 1 < @lines.size
-
- line = @lines[@line + 1]
- return false if line.empty?
-
- line_is_all? line, char
- end
-
- def line_is_all?(line, char)
- line.each_byte do |byte|
- return false if byte != char.ord
- end
- true
- end
-
- def count_pounds(line)
- bytesize = line.bytesize
- str = line.to_unsafe
- pos = 0
- while pos < bytesize && pos < 6 && str[pos].unsafe_chr == '#'
- pos += 1
- end
- pos == 0 ? nil : pos
- end
-
- def has_code_spaces?(line)
- bytesize = line.bytesize
- str = line.to_unsafe
- pos = 0
- while pos < bytesize && pos < 4 && str[pos].unsafe_chr.ascii_whitespace?
- pos += 1
- end
-
- if pos < 4
- pos == bytesize
- else
- true
- end
- end
-
- def starts_with_bullet_list_marker?(line, prefix = nil)
- bytesize = line.bytesize
- str = line.to_unsafe
- pos = 0
- while pos < bytesize && str[pos].unsafe_chr.ascii_whitespace?
- pos += 1
- end
-
- return false unless pos < bytesize
- return false unless prefix ? str[pos].unsafe_chr == prefix : (str[pos].unsafe_chr == '*' || str[pos].unsafe_chr == '-' || str[pos].unsafe_chr == '+')
-
- pos += 1
-
- return false unless pos < bytesize
- str[pos].unsafe_chr.ascii_whitespace?
- end
-
- def previous_line_is_not_intended_and_starts_with_bullet_list_marker?(prefix)
- previous_line = @lines[@line - 1]
- !previous_line.starts_with?(" ") && starts_with_bullet_list_marker?(previous_line, prefix)
- end
-
- def next_line_is_not_intended?
- return true unless @line + 1 < @lines.size
-
- next_line = @lines[@line + 1]
- !next_line.starts_with?(" ")
- end
-
- def code_fence?(line)
- return nil unless line.starts_with?("```")
- language = line.lstrip('`').strip
- return nil if language.includes? '`'
- CodeFence.new(language)
- end
-
- def starts_with_digits_dot?(line)
- bytesize = line.bytesize
- str = line.to_unsafe
- pos = 0
- while pos < bytesize && str[pos].unsafe_chr.ascii_whitespace?
- pos += 1
- end
-
- return false unless pos < bytesize
- return false unless str[pos].unsafe_chr.ascii_number?
-
- while pos < bytesize && str[pos].unsafe_chr.ascii_number?
- pos += 1
- end
-
- return false unless pos < bytesize
- str[pos].unsafe_chr == '.'
- end
-
- def next_lines_empty_of_code?
- line_number = @line
-
- while line_number < @lines.size
- line = @lines[line_number]
-
- if empty? line
- # Nothing
- elsif has_code_spaces? line
- return false
- else
- return true
- end
-
- line_number += 1
- end
-
- return true
- end
-
- def horizontal_rule?(line)
- non_space_char = nil
- count = 1
-
- line.each_char do |char|
- next if char.ascii_whitespace?
-
- if non_space_char
- if char == non_space_char
- count += 1
- else
- return false
- end
- else
- case char
- when '*', '-', '_'
- non_space_char = char
- else
- return false
- end
- end
- end
-
- count >= 3
- end
-
- def render_horizontal_rule
- @renderer.horizontal_rule
- @line += 1
- end
-
- def newline
- @renderer.text "\n"
- end
-
- # Join this line with next lines if they form a paragraph,
- # until next lines don't start another entity like a list,
- # header, etc.
- def join_next_lines(continue_on = :none, stop_on = :none)
- start = @line
- line = @line
- line += 1
- while line < @lines.size
- item = classify(@lines[line])
-
- case item
- when continue_on
- # continue
- when stop_on
- line -= 1
- break
- when nil
- # paragraph: continue
- else
- line -= 1
- break
- end
-
- line += 1
- end
- line -= 1 if line == @lines.size
-
- if line > start
- @lines[line] = (start..line).join('\n') { |i| @lines[i] }
- @line = line
- end
- end
-end
diff --git a/src/compiler/crystal/tools/doc/markdown/renderer.cr b/src/compiler/crystal/tools/doc/markdown/renderer.cr
deleted file mode 100644
index b278ac3e827b..000000000000
--- a/src/compiler/crystal/tools/doc/markdown/renderer.cr
+++ /dev/null
@@ -1,27 +0,0 @@
-module Crystal::Doc::Markdown::Renderer
- abstract def begin_paragraph
- abstract def end_paragraph
- abstract def begin_italic
- abstract def end_italic
- abstract def begin_bold
- abstract def end_bold
- abstract def begin_header(level : Int32, anchor : String) : Nil
- abstract def end_header(level)
- abstract def begin_inline_code
- abstract def end_inline_code
- abstract def begin_code(language)
- abstract def end_code
- abstract def begin_quote
- abstract def end_quote
- abstract def begin_unordered_list
- abstract def end_unordered_list
- abstract def begin_ordered_list
- abstract def end_ordered_list
- abstract def begin_list_item
- abstract def end_list_item
- abstract def begin_link(url)
- abstract def end_link
- abstract def image(url, alt)
- abstract def text(text)
- abstract def horizontal_rule
-end
diff --git a/src/compiler/crystal/tools/doc/type.cr b/src/compiler/crystal/tools/doc/type.cr
index faa09f51bf50..6c10b70e646a 100644
--- a/src/compiler/crystal/tools/doc/type.cr
+++ b/src/compiler/crystal/tools/doc/type.cr
@@ -176,7 +176,7 @@ class Crystal::Doc::Type
defs << method(def_with_metadata.def, false)
end
end
- defs.sort_by!(stable: true, &.name.downcase)
+ defs.sort_by!(&.name.downcase)
end
end
end
@@ -201,7 +201,7 @@ class Crystal::Doc::Type
end
end
end
- class_methods.sort_by!(stable: true, &.name.downcase)
+ class_methods.sort_by!(&.name.downcase)
end
end
@@ -225,7 +225,7 @@ class Crystal::Doc::Type
end
end
end
- macros.sort_by!(stable: true, &.name.downcase)
+ macros.sort_by!(&.name.downcase)
end
end
@@ -786,7 +786,7 @@ class Crystal::Doc::Type
builder.field "full_name", full_name
builder.field "name", name
builder.field "abstract", abstract?
- builder.field "superclass" { superclass.try(&.to_json_simple(builder)) || builder.scalar(nil) }
+ builder.field "superclass" { (s = superclass) ? s.to_json_simple(builder) : builder.null }
builder.field "ancestors" do
builder.array do
ancestors.each &.to_json_simple(builder)
@@ -821,7 +821,7 @@ class Crystal::Doc::Type
including_types.each &.to_json_simple(builder)
end
end
- builder.field "namespace" { namespace.try(&.to_json_simple(builder)) || builder.scalar(nil) }
+ builder.field "namespace" { (n = namespace) ? n.to_json_simple(builder) : builder.null }
builder.field "doc", doc
builder.field "summary", formatted_summary
builder.field "class_methods", class_methods
diff --git a/src/compiler/crystal/tools/formatter.cr b/src/compiler/crystal/tools/formatter.cr
index 1ded41bea0b0..d5f56aed4d32 100644
--- a/src/compiler/crystal/tools/formatter.cr
+++ b/src/compiler/crystal/tools/formatter.cr
@@ -3568,7 +3568,16 @@ module Crystal
write " " unless has_parentheses
skip_space
- if exp.is_a?(TupleLiteral) && @token.type != :"{"
+ # If the number of consecutive `{`s starting a tuple literal is 1 less
+ # than the level of tuple nesting in the actual AST node, this means the
+ # parser synthesized a TupleLiteral from multiple expressions, e.g.
+ #
+ # return {1, 2}, 3, 4
+ # return { {1, 2}, 3, 4 }
+ #
+ # The tuple depth is 2 in both cases but only 1 leading curly brace is
+ # present on the first return.
+ if exp.is_a?(TupleLiteral) && opening_curly_brace_count < leading_tuple_depth(exp)
format_args(exp.elements, has_parentheses)
skip_space if has_parentheses
else
@@ -3582,6 +3591,26 @@ module Crystal
false
end
+ def opening_curly_brace_count
+ @lexer.peek_ahead do
+ count = 0
+ while @lexer.token.type == :"{"
+ count += 1
+ @lexer.next_token_skip_space_or_newline
+ end
+ count
+ end
+ end
+
+ def leading_tuple_depth(exp)
+ count = 0
+ while exp.is_a?(TupleLiteral)
+ count += 1
+ exp = exp.elements.first?
+ end
+ count
+ end
+
def visit(node : Yield)
if scope = node.scope
write_keyword :with, " "
diff --git a/src/compiler/crystal/tools/playground/server.cr b/src/compiler/crystal/tools/playground/server.cr
index b6ddd6d629c3..69b1f80b57db 100644
--- a/src/compiler/crystal/tools/playground/server.cr
+++ b/src/compiler/crystal/tools/playground/server.cr
@@ -4,7 +4,7 @@ require "http/server"
require "log"
require "ecr/macros"
require "compiler/crystal/tools/formatter"
-require "compiler/crystal/tools/doc/markdown"
+require "../../../../../lib/markd/src/markd"
module Crystal::Playground
Log = ::Log.for("crystal.playground")
@@ -247,7 +247,7 @@ module Crystal::Playground
end
if extname == ".md" || extname == ".cr"
- content = Crystal::Doc::Markdown.to_html(content)
+ content = Markd.to_html(content)
end
content
rescue e
diff --git a/src/compiler/crystal/tools/print_hierarchy.cr b/src/compiler/crystal/tools/print_hierarchy.cr
index 513a53c2574e..b8f4c09cbfba 100644
--- a/src/compiler/crystal/tools/print_hierarchy.cr
+++ b/src/compiler/crystal/tools/print_hierarchy.cr
@@ -3,23 +3,24 @@ require "colorize"
require "../syntax/ast"
module Crystal
- def self.print_hierarchy(program, exp, format)
+ def self.print_hierarchy(program, io, exp, format)
case format
when "text"
- HierarchyPrinter.new(program, exp).execute
+ TextHierarchyPrinter.new(program, io, exp).execute
when "json"
- JSONHierarchyPrinter.new(program, exp).execute
+ JSONHierarchyPrinter.new(program, io, exp).execute
else
raise "Unknown hierarchy format: #{format}"
end
end
- class HierarchyPrinter
+ abstract class HierarchyPrinter
+ abstract def print_all
+
@llvm_typer : LLVMTyper
def initialize(@program : Program, exp : String?)
@exp = exp ? Regex.new(exp) : nil
- @indents = [] of Bool
@targets = Set(Type).new
@llvm_typer = @program.llvm_typer
end
@@ -29,9 +30,7 @@ module Crystal
compute_targets(@program.types, exp, false)
end
- with_color.light_gray.bold.surround(STDOUT) do
- print_type @program.object
- end
+ print_all
end
def compute_targets(types : Array, exp, must_include = false)
@@ -95,6 +94,35 @@ module Crystal
false
end
+ def must_print?(type : NonGenericClassType | GenericClassType)
+ !@exp || @targets.includes?(type)
+ end
+
+ def must_print?(type)
+ false
+ end
+
+ def type_size(type)
+ @llvm_typer.size_of(@llvm_typer.llvm_struct_type(type))
+ end
+
+ def ivar_size(ivar)
+ @llvm_typer.size_of(@llvm_typer.llvm_embedded_type(ivar.type))
+ end
+ end
+
+ class TextHierarchyPrinter < HierarchyPrinter
+ def initialize(program : Program, @io : IO, exp : String?)
+ super(program, exp)
+ @indents = [] of Bool
+ end
+
+ def print_all
+ with_color.light_gray.bold.surround(@io) do
+ print_type @program.object
+ end
+ end
+
def print_subtypes(types)
types = types.sort_by &.to_s
types.each_with_index do |type, i|
@@ -110,8 +138,7 @@ module Crystal
unless @indents.empty?
print_indent
- print "|"
- puts
+ @io << "|\n"
end
print_type type
@@ -119,36 +146,19 @@ module Crystal
def print_type_name(type)
print_indent
- print "+" unless @indents.empty?
- print "- "
- print type.struct? ? "struct" : "class"
- print " "
- print type
+ @io << "+" unless @indents.empty?
+ @io << "- " << (type.struct? ? "struct" : "class") << " " << type
if (type.is_a?(NonGenericClassType) || type.is_a?(GenericClassInstanceType)) &&
!type.is_a?(PointerInstanceType) && !type.is_a?(ProcInstanceType)
- size = @llvm_typer.size_of(@llvm_typer.llvm_struct_type(type))
- with_color.light_gray.surround(STDOUT) do
- print " ("
- print size.to_s
- print " bytes)"
+ with_color.light_gray.surround(@io) do
+ @io << " (" << type_size(type) << " bytes)"
end
end
- puts
+ @io << '\n'
end
- def print_type(type : NonGenericClassType | GenericClassInstanceType)
- print_type_name type
-
- subtypes = type.subclasses.select { |sub| must_print?(sub) }
- print_instance_vars type, !subtypes.empty?
-
- with_indent do
- print_subtypes subtypes
- end
- end
-
- def print_type(type : GenericClassType)
+ def print_type(type : GenericClassType | NonGenericClassType | GenericClassInstanceType)
print_type_name type
subtypes = type.subclasses.select { |sub| must_print?(sub) }
@@ -171,19 +181,13 @@ module Crystal
instance_vars.each do |name, var|
print_indent
- print (@indents.last ? "|" : " ")
- if has_subtypes
- print " . "
- else
- print " "
- end
+ @io << (@indents.last ? "|" : " ") << (has_subtypes ? " . " : " ")
- with_color.light_gray.surround(STDOUT) do
- print name.ljust(max_name_size)
- print " : "
- print var
+ with_color.light_gray.surround(@io) do
+ name.ljust(@io, max_name_size)
+ @io << " : " << var
end
- puts
+ @io << '\n'
end
end
@@ -196,63 +200,40 @@ module Crystal
max_name_size = instance_vars.max_of &.name.size
- if typed_instance_vars.empty?
- max_type_size = 0
- max_bytes_size = 0
- else
- max_type_size = typed_instance_vars.max_of &.type.to_s.size
- max_bytes_size = typed_instance_vars.max_of { |var| @llvm_typer.size_of(@llvm_typer.llvm_embedded_type(var.type)).to_s.size }
- end
+ max_type_size = typed_instance_vars.max_of?(&.type.to_s.size) || 0
+ max_bytes_size = typed_instance_vars.max_of? { |var| ivar_size(var).to_s.size } || 0
instance_vars.each do |ivar|
print_indent
- print (@indents.last ? "|" : " ")
- if has_subtypes
- print " . "
- else
- print " "
- end
+ @io << (@indents.last ? "|" : " ") << (has_subtypes ? " . " : " ")
- with_color.light_gray.surround(STDOUT) do
- print ivar.name.ljust(max_name_size)
- print " : "
+ with_color.light_gray.surround(@io) do
+ ivar.name.ljust(@io, max_name_size)
+ @io << " : "
if ivar_type = ivar.type?
- print ivar_type.to_s.ljust(max_type_size)
- size = @llvm_typer.size_of(@llvm_typer.llvm_embedded_type(ivar_type))
- with_color.light_gray.surround(STDOUT) do
- print " ("
- print size.to_s.rjust(max_bytes_size)
- print " bytes)"
+ ivar_type.to_s.ljust(@io, max_type_size)
+ with_color.light_gray.surround(@io) do
+ @io << " ("
+ ivar_size(ivar).to_s.rjust(@io, max_bytes_size)
+ @io << " bytes)"
end
else
- print "MISSING".colorize.red.bright
+ @io << "MISSING".colorize.red.bright
end
end
- puts
+ @io << '\n'
end
end
- def must_print?(type : NonGenericClassType)
- !(@exp && !@targets.includes?(type))
- end
-
- def must_print?(type : GenericClassType)
- !(@exp && !@targets.includes?(type))
- end
-
- def must_print?(type)
- false
- end
-
def print_indent
unless @indents.empty?
- print " "
+ @io << " "
0.upto(@indents.size - 2) do |i|
indent = @indents[i]
if indent
- print "| "
+ @io << "| "
else
- print " "
+ @io << " "
end
end
end
@@ -270,27 +251,28 @@ module Crystal
end
class JSONHierarchyPrinter < HierarchyPrinter
- def execute
- if exp = @exp
- compute_targets(@program.types, exp, false)
- end
+ def initialize(program : Program, io : IO, exp : String?)
+ super(program, exp)
+ @json = JSON::Builder.new(io)
+ end
- JSON.build(STDOUT) do |json|
- json.object do
- print_type(@program.object, json)
+ def print_all
+ @json.document do
+ @json.object do
+ print_type(@program.object)
end
end
end
- def print_subtypes(types, json)
+ def print_subtypes(types)
types = types.sort_by &.to_s
- json.field "sub_types" do
- json.array do
+ @json.field "sub_types" do
+ @json.array do
types.each_with_index do |type, index|
if must_print? type
- json.object do
- print_type(type, json)
+ @json.object do
+ print_type(type)
end
end
end
@@ -298,57 +280,57 @@ module Crystal
end
end
- def print_type_name(type, json)
- json.field "name", type.to_s
- json.field "kind", type.struct? ? "struct" : "class"
+ def print_type_name(type)
+ @json.field "name", type.to_s
+ @json.field "kind", type.struct? ? "struct" : "class"
if (type.is_a?(NonGenericClassType) || type.is_a?(GenericClassInstanceType)) &&
!type.is_a?(PointerInstanceType) && !type.is_a?(ProcInstanceType)
- json.field "size_in_bytes", @llvm_typer.size_of(@llvm_typer.llvm_struct_type(type))
+ @json.field "size_in_bytes", type_size(type)
end
end
- def print_type(type : GenericClassType | NonGenericClassType | GenericClassInstanceType, json)
- print_type_name(type, json)
+ def print_type(type : GenericClassType | NonGenericClassType | GenericClassInstanceType)
+ print_type_name(type)
subtypes = type.subclasses.select { |sub| must_print?(sub) }
- print_instance_vars(type, !subtypes.empty?, json)
- print_subtypes(subtypes, json)
+ print_instance_vars(type, !subtypes.empty?)
+ print_subtypes(subtypes)
end
- def print_type(type, json)
+ def print_type(type)
# Nothing to do
end
- def print_instance_vars(type : GenericClassType, has_subtypes, json)
+ def print_instance_vars(type : GenericClassType, has_subtypes)
instance_vars = type.instance_vars
return if instance_vars.empty?
- json.field "instance_vars" do
- json.array do
+ @json.field "instance_vars" do
+ @json.array do
instance_vars.each do |name, var|
- json.object do
- json.field "name", name.to_s
- json.field "type", var.to_s
+ @json.object do
+ @json.field "name", name.to_s
+ @json.field "type", var.to_s
end
end
end
end
end
- def print_instance_vars(type, has_subtypes, json)
+ def print_instance_vars(type, has_subtypes)
instance_vars = type.instance_vars
return if instance_vars.empty?
instance_vars = instance_vars.values
- json.field "instance_vars" do
- json.array do
+ @json.field "instance_vars" do
+ @json.array do
instance_vars.each do |instance_var|
if ivar_type = instance_var.type?
- json.object do
- json.field "name", instance_var.name.to_s
- json.field "type", ivar_type.to_s
- json.field "size_in_bytes", @llvm_typer.size_of(@llvm_typer.llvm_embedded_type(ivar_type))
+ @json.object do
+ @json.field "name", instance_var.name.to_s
+ @json.field "type", ivar_type.to_s
+ @json.field "size_in_bytes", ivar_size(instance_var)
end
end
end
diff --git a/src/compiler/crystal/types.cr b/src/compiler/crystal/types.cr
index c0188f475c03..a527208a4bbf 100644
--- a/src/compiler/crystal/types.cr
+++ b/src/compiler/crystal/types.cr
@@ -294,8 +294,8 @@ module Crystal
end
end
- def filter_by(other_type)
- restrict other_type, MatchContext.new(self, self, strict: true)
+ def filter_by(other_type : Type)
+ Type.common_descendent(self, other_type)
end
def filter_by_responds_to(name)
@@ -1603,6 +1603,8 @@ module Crystal
end
def run_instance_var_initializer(initializer, instance : GenericClassInstanceType | NonGenericClassType)
+ return if instance.unbound?
+
meta_vars = MetaVars.new
visitor = MainVisitor.new(program, vars: meta_vars, meta_vars: meta_vars)
visitor.scope = instance.metaclass
@@ -3152,6 +3154,9 @@ module Crystal
property? used = false
property? visited = false
+ # Was this const's value cleaned up by CleanupTransformer yet?
+ property? cleaned_up = false
+
# Is this constant accessed with pointerof(...)?
property? pointer_read = false
@@ -3339,6 +3344,10 @@ module Crystal
delegate lookup_first_def, to: instance_type.metaclass
+ def replace_type_parameters(instance)
+ base_type.replace_type_parameters(instance).virtual_type.metaclass
+ end
+
def each_concrete_type
instance_type.subtypes.each do |type|
yield type.metaclass
diff --git a/src/compress/deflate/reader.cr b/src/compress/deflate/reader.cr
index 9a185d809a9c..bde3a187d0fb 100644
--- a/src/compress/deflate/reader.cr
+++ b/src/compress/deflate/reader.cr
@@ -130,7 +130,7 @@ class Compress::Deflate::Reader < IO
end
# Closes this reader.
- def unbuffered_close
+ def unbuffered_close : Nil
return if @closed
@closed = true
@@ -140,7 +140,7 @@ class Compress::Deflate::Reader < IO
@io.close if @sync_close
end
- def unbuffered_rewind
+ def unbuffered_rewind : Nil
check_open
@io.rewind
diff --git a/src/compress/deflate/writer.cr b/src/compress/deflate/writer.cr
index e844604f9b6a..1efcef87cdaa 100644
--- a/src/compress/deflate/writer.cr
+++ b/src/compress/deflate/writer.cr
@@ -54,7 +54,7 @@ class Compress::Deflate::Writer < IO
end
# See `IO#flush`.
- def flush
+ def flush : Nil
return if @closed
consume_output LibZ::Flush::SYNC_FLUSH
@@ -62,7 +62,7 @@ class Compress::Deflate::Writer < IO
end
# Closes this writer. Must be invoked after all data has been written.
- def close
+ def close : Nil
return if @closed
@closed = true
diff --git a/src/compress/gzip/reader.cr b/src/compress/gzip/reader.cr
index 63b5530dcad7..56dac99326d9 100644
--- a/src/compress/gzip/reader.cr
+++ b/src/compress/gzip/reader.cr
@@ -138,7 +138,7 @@ class Compress::Gzip::Reader < IO
end
# Closes this reader.
- def unbuffered_close
+ def unbuffered_close : Nil
return if @closed
@closed = true
@@ -146,7 +146,7 @@ class Compress::Gzip::Reader < IO
@io.close if @sync_close
end
- def unbuffered_rewind
+ def unbuffered_rewind : Nil
check_open
@io.rewind
diff --git a/src/compress/gzip/writer.cr b/src/compress/gzip/writer.cr
index 9cdbe02a370c..3d82548e2d2c 100644
--- a/src/compress/gzip/writer.cr
+++ b/src/compress/gzip/writer.cr
@@ -88,7 +88,7 @@ class Compress::Gzip::Writer < IO
# data has been written yet.
#
# See `IO#flush`.
- def flush
+ def flush : Nil
check_open
flate_io = write_header
@@ -96,7 +96,7 @@ class Compress::Gzip::Writer < IO
end
# Closes this writer. Must be invoked after all data has been written.
- def close
+ def close : Nil
return if @closed
@closed = true
diff --git a/src/compress/zip/file.cr b/src/compress/zip/file.cr
index 6ac8f6972022..843b0758f0a4 100644
--- a/src/compress/zip/file.cr
+++ b/src/compress/zip/file.cr
@@ -75,7 +75,7 @@ class Compress::Zip::File
end
# Closes this zip file.
- def close
+ def close : Nil
return if @closed
@closed = true
if @sync_close
diff --git a/src/compress/zip/reader.cr b/src/compress/zip/reader.cr
index 080620536a97..9763eb71d2e4 100644
--- a/src/compress/zip/reader.cr
+++ b/src/compress/zip/reader.cr
@@ -101,7 +101,7 @@ class Compress::Zip::Reader
end
# Closes this zip reader.
- def close
+ def close : Nil
return if @closed
@closed = true
@io.close if @sync_close
diff --git a/src/compress/zip/writer.cr b/src/compress/zip/writer.cr
index 2acd1570765d..5c5741d7371c 100644
--- a/src/compress/zip/writer.cr
+++ b/src/compress/zip/writer.cr
@@ -151,14 +151,14 @@ class Compress::Zip::Writer
end
# Adds an entry that will have *string* as its contents.
- def add(filename_or_entry : String | Entry, string : String)
+ def add(filename_or_entry : String | Entry, string : String) : Nil
add(filename_or_entry) do |io|
io << string
end
end
# Adds an entry that will have *bytes* as its contents.
- def add(filename_or_entry : String | Entry, bytes : Bytes)
+ def add(filename_or_entry : String | Entry, bytes : Bytes) : Nil
add(filename_or_entry) do |io|
io.write(bytes)
end
@@ -167,7 +167,7 @@ class Compress::Zip::Writer
# Adds an entry that will have its data copied from the given *data*.
# If the given *data* is a `::File`, it is automatically closed
# after data is copied from it.
- def add(filename_or_entry : String | Entry, data : IO)
+ def add(filename_or_entry : String | Entry, data : IO) : Nil
add(filename_or_entry) do |io|
IO.copy(data, io)
data.close if data.is_a?(::File)
@@ -175,13 +175,13 @@ class Compress::Zip::Writer
end
# Adds a directory entry that will have the given *name*.
- def add_dir(name)
+ def add_dir(name) : Nil
name = name + '/' unless name.ends_with?('/')
add(Entry.new(name)) { }
end
# Closes this zip writer.
- def close
+ def close : Nil
return if @closed
@closed = true
diff --git a/src/compress/zlib/reader.cr b/src/compress/zlib/reader.cr
index df6eecb84ffe..953e313e255a 100644
--- a/src/compress/zlib/reader.cr
+++ b/src/compress/zlib/reader.cr
@@ -89,7 +89,7 @@ class Compress::Zlib::Reader < IO
raise IO::Error.new "Can't flush Compress::Zlib::Reader"
end
- def unbuffered_close
+ def unbuffered_close : Nil
return if @closed
@closed = true
@@ -97,7 +97,7 @@ class Compress::Zlib::Reader < IO
@io.close if @sync_close
end
- def unbuffered_rewind
+ def unbuffered_rewind : Nil
check_open
@io.rewind
diff --git a/src/compress/zlib/writer.cr b/src/compress/zlib/writer.cr
index 03e27cf07ab5..bb711be40dc0 100644
--- a/src/compress/zlib/writer.cr
+++ b/src/compress/zlib/writer.cr
@@ -59,7 +59,7 @@ class Compress::Zlib::Writer < IO
# data has been written yet.
#
# See `IO#flush`.
- def flush
+ def flush : Nil
check_open
write_header unless @wrote_header
@@ -67,7 +67,7 @@ class Compress::Zlib::Writer < IO
end
# Closes this writer. Must be invoked after all data has been written.
- def close
+ def close : Nil
return if @closed
@closed = true
diff --git a/src/crystal/system/unix/file.cr b/src/crystal/system/unix/file.cr
index a6808e21d9a9..850a32fa0f28 100644
--- a/src/crystal/system/unix/file.cr
+++ b/src/crystal/system/unix/file.cr
@@ -130,10 +130,10 @@ module Crystal::System::File
raise ::File::Error.from_os_error("Cannot read link", Errno::ENAMETOOLONG, file: path)
end
- def self.rename(old_filename, new_filename)
+ def self.rename(old_filename, new_filename) : ::File::Error?
code = LibC.rename(old_filename.check_no_null_byte, new_filename.check_no_null_byte)
if code != 0
- raise ::File::Error.from_errno("Error renaming file", file: old_filename, other: new_filename)
+ ::File::Error.from_errno("Error renaming file", file: old_filename, other: new_filename)
end
end
diff --git a/src/crystal/system/win32/file.cr b/src/crystal/system/win32/file.cr
index 8ecdbb6893bc..756e443976e2 100644
--- a/src/crystal/system/win32/file.cr
+++ b/src/crystal/system/win32/file.cr
@@ -203,9 +203,9 @@ module Crystal::System::File
raise NotImplementedError.new("readlink")
end
- def self.rename(old_path : String, new_path : String) : Nil
+ def self.rename(old_path : String, new_path : String) : ::File::Error?
if LibC.MoveFileExW(to_windows_path(old_path), to_windows_path(new_path), LibC::MOVEFILE_REPLACE_EXISTING) == 0
- raise ::File::Error.from_winerror("Error renaming file", file: old_path, other: new_path)
+ ::File::Error.from_winerror("Error renaming file", file: old_path, other: new_path)
end
end
diff --git a/src/csv.cr b/src/csv.cr
index 1b8e89e07a1f..494931d8f10e 100644
--- a/src/csv.cr
+++ b/src/csv.cr
@@ -296,7 +296,7 @@ class CSV
end
# Rewinds this CSV to the beginning, rewinding the underlying IO if any.
- def rewind
+ def rewind : Nil
@parser.rewind
@parser.next_row if @headers
@traversed = false
diff --git a/src/csv/builder.cr b/src/csv/builder.cr
index 49c8c2149cbd..49410f9647ac 100644
--- a/src/csv/builder.cr
+++ b/src/csv/builder.cr
@@ -60,7 +60,7 @@ class CSV::Builder
end
# Appends the given values as a single row, and then a newline.
- def row(values : Enumerable)
+ def row(values : Enumerable) : Nil
row do |row|
values.each do |value|
row << value
@@ -69,7 +69,7 @@ class CSV::Builder
end
# :ditto:
- def row(*values)
+ def row(*values) : Nil
row values
end
@@ -111,7 +111,7 @@ class CSV::Builder
end
# Appends the given value to this row.
- def <<(value : String)
+ def <<(value : String) : Nil
if needs_quotes?(value)
@builder.quote_cell value
else
@@ -120,7 +120,7 @@ class CSV::Builder
end
# :ditto:
- def <<(value : Nil | Bool | Number)
+ def <<(value : Nil | Bool | Number) : Nil
case @quoting
when .all?
@builder.cell { |io|
@@ -134,7 +134,7 @@ class CSV::Builder
end
# :ditto:
- def <<(value)
+ def <<(value) : Nil
self << value.to_s
end
@@ -151,7 +151,7 @@ class CSV::Builder
end
# Appends a comma, thus skipping a cell.
- def skip_cell
+ def skip_cell : Nil
self << nil
end
diff --git a/src/csv/lexer.cr b/src/csv/lexer.cr
index 2656d53656b1..634ec716b303 100644
--- a/src/csv/lexer.cr
+++ b/src/csv/lexer.cr
@@ -39,7 +39,7 @@ abstract class CSV::Lexer
end
# Rewinds this lexer to the beginning
- def rewind
+ def rewind : Nil
@column_number = 1
@line_number = 1
@last_empty_column = false
diff --git a/src/csv/lexer/io_based.cr b/src/csv/lexer/io_based.cr
index 253e0b2f8a3f..f33e83d99d9e 100644
--- a/src/csv/lexer/io_based.cr
+++ b/src/csv/lexer/io_based.cr
@@ -7,7 +7,7 @@ class CSV::Lexer::IOBased < CSV::Lexer
@current_char = @io.read_char || '\0'
end
- def rewind
+ def rewind : Nil
super
@io.rewind
@current_char = @io.read_char || '\0'
diff --git a/src/csv/lexer/string_based.cr b/src/csv/lexer/string_based.cr
index fdffd072ed6e..c9d1193adc34 100644
--- a/src/csv/lexer/string_based.cr
+++ b/src/csv/lexer/string_based.cr
@@ -11,7 +11,7 @@ class CSV::Lexer::StringBased < CSV::Lexer
end
end
- def rewind
+ def rewind : Nil
super
@reader.pos = 0
if @reader.current_char == '\n'
diff --git a/src/csv/parser.cr b/src/csv/parser.cr
index 987e3bb150b9..ae3ec7c890d8 100644
--- a/src/csv/parser.cr
+++ b/src/csv/parser.cr
@@ -54,7 +54,7 @@ class CSV::Parser
end
# Rewinds this parser to the beginning.
- def rewind
+ def rewind : Nil
@lexer.rewind
end
diff --git a/src/deque.cr b/src/deque.cr
index 0d55c3abe196..03c7ec4f232c 100644
--- a/src/deque.cr
+++ b/src/deque.cr
@@ -493,7 +493,7 @@ class Deque(T)
#
# * For positive *n*, equivalent to `n.times { push(shift) }`.
# * For negative *n*, equivalent to `(-n).times { unshift(pop) }`.
- def rotate!(n : Int = 1)
+ def rotate!(n : Int = 1) : Nil
return if @size <= 1
if @size == @capacity
@start = (@start + n) % @capacity
diff --git a/src/enumerable.cr b/src/enumerable.cr
index bebb24eff5f8..d8ad4204bcae 100644
--- a/src/enumerable.cr
+++ b/src/enumerable.cr
@@ -34,7 +34,7 @@ module Enumerable(T)
end
# Must yield this collection's elements to the block.
- abstract def each(&block : T -> _)
+ abstract def each(&block : T ->)
# Returns `true` if the passed block returns a value other than `false` or `nil`
# for all elements of the collection.
@@ -222,7 +222,7 @@ module Enumerable(T)
# ["Alice", "Bob"].compact_map { |name| name.match(/^A./) } # => [Regex::MatchData("Al")]
# ```
def compact_map
- ary = [] of typeof((yield first).not_nil!)
+ ary = [] of typeof((yield Enumerable.element_type(self)).not_nil!)
each do |e|
v = yield e
unless v.is_a?(Nil)
@@ -525,7 +525,7 @@ module Enumerable(T)
# array # => ['A', 'l', 'i', 'c', 'e', 'B', 'o', 'b']
# ```
def flat_map(&block : T -> _)
- ary = [] of typeof(flat_map_type(yield first))
+ ary = [] of typeof(flat_map_type(yield Enumerable.element_type(self)))
each do |e|
case v = yield e
when Array, Iterator
@@ -668,14 +668,26 @@ module Enumerable(T)
# For each element in the collection the block is passed an accumulator value (*memo*) and the element. The
# result becomes the new value for *memo*. At the end of the iteration, the final value of *memo* is
# the return value for the method. The initial value for the accumulator is the first element in the collection.
+ # If the collection has only one element, that element is returned.
#
# Raises `Enumerable::EmptyError` if the collection is empty.
#
# ```
# [1, 2, 3, 4, 5].reduce { |acc, i| acc + i } # => 15
+ # [1].reduce { |acc, i| acc + i } # => 1
+ # ([] of Int32).reduce { |acc, i| acc + i } # raises Enumerable::EmptyError
+ # ```
+ #
+ # The block is not required to return a `T`, in which case the accumulator's
+ # type includes whatever the block returns.
+ #
+ # ```
+ # # `acc` is an `Int32 | String`
+ # [1, 2, 3, 4, 5].reduce { |acc, i| "#{acc}-#{i}" } # => "1-2-3-4-5"
+ # [1].reduce { |acc, i| "#{acc}-#{i}" } # => 1
# ```
def reduce
- memo = uninitialized T
+ memo = uninitialized typeof(reduce(Enumerable.element_type(self)) { |acc, i| yield acc, i })
found = false
each do |elem|
@@ -706,7 +718,7 @@ module Enumerable(T)
# ([] of Int32).reduce? { |acc, i| acc + i } # => nil
# ```
def reduce?
- memo = uninitialized T
+ memo = uninitialized typeof(reduce(Enumerable.element_type(self)) { |acc, i| yield acc, i })
found = false
each do |elem|
@@ -1304,7 +1316,7 @@ module Enumerable(T)
# ```
def reject(type : U.class) forall U
ary = [] of typeof(begin
- e = first
+ e = Enumerable.element_type(self)
e.is_a?(U) ? raise("") : e
end)
each { |e| ary << e unless e.is_a?(U) }
@@ -1551,7 +1563,7 @@ module Enumerable(T)
# ([] of Int32).sum { |x| x + 1 } # => 0
# ```
def sum(&block)
- sum(additive_identity(Reflect(typeof(yield first)))) do |value|
+ sum(additive_identity(Reflect(typeof(yield Enumerable.element_type(self))))) do |value|
yield value
end
end
@@ -1630,7 +1642,7 @@ module Enumerable(T)
# ([] of Int32).product { |x| x + 1 } # => 1
# ```
def product(&block)
- product(Reflect(typeof(yield first)).first.multiplicative_identity) do |value|
+ product(Reflect(typeof(yield Enumerable.element_type(self))).first.multiplicative_identity) do |value|
yield value
end
end
@@ -1721,7 +1733,7 @@ module Enumerable(T)
# Tuple.new({:a, 1}, {:c, 2}).to_h # => {:a => 1, :c => 2}
# ```
def to_h
- each_with_object(Hash(typeof(first[0]), typeof(first[1])).new) do |item, hash|
+ each_with_object(Hash(typeof(Enumerable.element_type(self)[0]), typeof(Enumerable.element_type(self)[1])).new) do |item, hash|
hash[item[0]] = item[1]
end
end
diff --git a/src/exception/call_stack.cr b/src/exception/call_stack.cr
index f154dfe6f600..eb4045cedf8e 100644
--- a/src/exception/call_stack.cr
+++ b/src/exception/call_stack.cr
@@ -29,7 +29,7 @@ struct Exception::CallStack
@@skip = [] of String
- def self.skip(filename)
+ def self.skip(filename) : Nil
@@skip << filename
end
@@ -106,7 +106,7 @@ struct Exception::CallStack
end
end
- def self.print_backtrace
+ def self.print_backtrace : Nil
backtrace_fn = ->(context : LibUnwind::Context, data : Void*) do
last_frame = data.as(RepeatedFrame*)
diff --git a/src/fiber/stack_pool.cr b/src/fiber/stack_pool.cr
index f3527b949d22..54d03e4ffa5f 100644
--- a/src/fiber/stack_pool.cr
+++ b/src/fiber/stack_pool.cr
@@ -29,7 +29,7 @@ class Fiber
end
# Appends a stack to the bottom of the pool.
- def release(stack)
+ def release(stack) : Nil
@mutex.synchronize { @deque.push(stack) }
end
diff --git a/src/file.cr b/src/file.cr
index ccbc61384d39..3659a7f50792 100644
--- a/src/file.cr
+++ b/src/file.cr
@@ -398,7 +398,7 @@ class File < IO::FileDescriptor
File.expand_brace_pattern(pattern, expanded_patterns)
expanded_patterns.each do |expanded_pattern|
- return true if match_single_pattern(expanded_pattern, path)
+ return true if match_single_pattern(expanded_pattern, path.to_s)
end
false
end
@@ -587,12 +587,12 @@ class File < IO::FileDescriptor
# Creates a new link (also known as a hard link) at *new_path* to an existing file
# given by *old_path*.
- def self.link(old_path : Path | String, new_path : Path | String)
+ def self.link(old_path : Path | String, new_path : Path | String) : Nil
Crystal::System::File.link(old_path.to_s, new_path.to_s)
end
# Creates a symbolic link at *new_path* to an existing file given by *old_path*.
- def self.symlink(old_path : Path | String, new_path : Path | String)
+ def self.symlink(old_path : Path | String, new_path : Path | String) : Nil
Crystal::System::File.symlink(old_path.to_s, new_path.to_s)
end
@@ -773,7 +773,9 @@ class File < IO::FileDescriptor
# File.exists?("afile.cr") # => true
# ```
def self.rename(old_filename : Path | String, new_filename : Path | String) : Nil
- Crystal::System::File.rename(old_filename.to_s, new_filename.to_s)
+ if error = Crystal::System::File.rename(old_filename.to_s, new_filename.to_s)
+ raise error
+ end
end
# Sets the access and modification times of *filename*.
diff --git a/src/file/preader.cr b/src/file/preader.cr
index e4999e80df53..9bd9fb73d490 100644
--- a/src/file/preader.cr
+++ b/src/file/preader.cr
@@ -35,11 +35,11 @@ class File::PReader < IO
raise IO::Error.new("Can't flush read-only IO")
end
- def unbuffered_rewind
+ def unbuffered_rewind : Nil
@pos = 0
end
- def unbuffered_close
+ def unbuffered_close : Nil
@closed = true
end
end
diff --git a/src/file_utils.cr b/src/file_utils.cr
index ca424ddb6cbb..e82e3c1e15f5 100644
--- a/src/file_utils.cr
+++ b/src/file_utils.cr
@@ -10,7 +10,7 @@ module FileUtils
# ```
#
# NOTE: Alias of `Dir.cd`
- def cd(path : Path | String)
+ def cd(path : Path | String) : Nil
Dir.cd(path)
end
@@ -89,7 +89,7 @@ module FileUtils
# File.info("afile_copy").permissions.value # => 0o600
# ```
def cp(src_path : Path | String, dest : Path | String) : Nil
- dest += File::SEPARATOR + File.basename(src_path) if Dir.exists?(dest)
+ dest = Path[dest, File.basename(src_path)] if Dir.exists?(dest)
File.copy(src_path, dest)
end
@@ -147,7 +147,7 @@ module FileUtils
# # Create a hard link, pointing from /tmp/foo.c to foo.c
# FileUtils.ln("foo.c", "/tmp")
# ```
- def ln(src_path : Path | String, dest_path : Path | String)
+ def ln(src_path : Path | String, dest_path : Path | String) : Nil
if Dir.exists?(dest_path)
File.link(src_path, File.join(dest_path, File.basename(src_path)))
else
@@ -183,7 +183,7 @@ module FileUtils
# # Create a symbolic link pointing from /tmp/src to src
# FileUtils.ln_s("src", "/tmp")
# ```
- def ln_s(src_path : Path | String, dest_path : Path | String)
+ def ln_s(src_path : Path | String, dest_path : Path | String) : Nil
if Dir.exists?(dest_path)
File.symlink(src_path, File.join(dest_path, File.basename(src_path)))
else
@@ -217,7 +217,7 @@ module FileUtils
# # Create a symbolic link pointing from bar.c to foo.c, even if bar.c already exists
# FileUtils.ln_sf("foo.c", "bar.c")
# ```
- def ln_sf(src_path : Path | String, dest_path : Path | String)
+ def ln_sf(src_path : Path | String, dest_path : Path | String) : Nil
if File.directory?(dest_path)
dest_path = File.join(dest_path, File.basename(src_path))
end
@@ -306,15 +306,20 @@ module FileUtils
# Moves *src_path* to *dest_path*.
#
+ # NOTE: If *src_path* and *dest_path* exist on different mounted filesystems,
+ # the file at *src_path* is copied to *dest_path* and then removed.
+ #
# ```
# require "file_utils"
#
# FileUtils.mv("afile", "afile.cr")
# ```
- #
- # NOTE: Alias of `File.rename`
def mv(src_path : Path | String, dest_path : Path | String) : Nil
- File.rename(src_path, dest_path)
+ if error = Crystal::System::File.rename(src_path.to_s, dest_path.to_s)
+ raise error unless Errno.value.in?(Errno::EXDEV, Errno::EPERM)
+ cp_r(src_path, dest_path)
+ rm_r(src_path)
+ end
end
# Moves every *srcs* to *dest*.
diff --git a/src/float.cr b/src/float.cr
index 6a80d730fbf4..79308547933e 100644
--- a/src/float.cr
+++ b/src/float.cr
@@ -181,6 +181,16 @@ struct Float32
LibM.trunc_f32(self)
end
+ # Returns the least `Float32` that is greater than `self`.
+ def next_float : Float32
+ LibM.nextafter_f32(self, INFINITY)
+ end
+
+ # Returns the greatest `Float32` that is less than `self`.
+ def prev_float : Float32
+ LibM.nextafter_f32(self, -INFINITY)
+ end
+
def **(other : Int32)
{% if flag?(:win32) %}
self ** other.to_f32
@@ -288,6 +298,16 @@ struct Float64
LibM.trunc_f64(self)
end
+ # Returns the least `Float64` that is greater than `self`.
+ def next_float : Float64
+ LibM.nextafter_f64(self, INFINITY)
+ end
+
+ # Returns the greatest `Float64` that is less than `self`.
+ def prev_float : Float64
+ LibM.nextafter_f64(self, -INFINITY)
+ end
+
def **(other : Int32)
{% if flag?(:win32) %}
self ** other.to_f64
diff --git a/src/float/printer.cr b/src/float/printer.cr
index 244cd9b36559..bd1b307410a6 100644
--- a/src/float/printer.cr
+++ b/src/float/printer.cr
@@ -47,7 +47,7 @@ module Float::Printer
LibC.snprintf(buffer.to_unsafe, BUFFER_SIZE, "%g", v.to_f64)
end
len = LibC.strlen(buffer)
- io.write_utf8 buffer.to_slice[0, len]
+ io.write_string buffer.to_slice[0, len]
return
end
@@ -65,11 +65,11 @@ module Float::Printer
# add integer part digits
if decimal_exponent > 0 && !exp_mode
# whole number but not big enough to be exp form
- io.write_utf8 buffer.to_slice[i, length - i]
+ io.write_string buffer.to_slice[i, length - i]
i = length
(point - length).times { io << '0' }
elsif i < point
- io.write_utf8 buffer.to_slice[i, point - i]
+ io.write_string buffer.to_slice[i, point - i]
i = point
end
@@ -81,7 +81,7 @@ module Float::Printer
end
# add fractional part digits
- io.write_utf8 buffer.to_slice[i, length - i]
+ io.write_string buffer.to_slice[i, length - i]
i = length
# print trailing 0 if whole number or exp notation of power of ten
diff --git a/src/http/client.cr b/src/http/client.cr
index be561d7b6d35..e0e8f25a00d5 100644
--- a/src/http/client.cr
+++ b/src/http/client.cr
@@ -259,7 +259,7 @@ class HTTP::Client
# Configures this client to perform basic authentication in every
# request.
- def basic_auth(username, password)
+ def basic_auth(username, password) : Nil
header = "Basic #{Base64.strict_encode("#{username}:#{password}")}"
before_request do |request|
request.headers["Authorization"] = header
@@ -398,7 +398,7 @@ class HTTP::Client
# end
# client.get "/"
# ```
- def before_request(&callback : HTTP::Request ->)
+ def before_request(&callback : HTTP::Request ->) : Nil
before_request = @before_request ||= [] of (HTTP::Request ->)
before_request << callback
end
diff --git a/src/http/cookie.cr b/src/http/cookie.cr
index 0ef0b05c0b90..a5707d386cb6 100644
--- a/src/http/cookie.cr
+++ b/src/http/cookie.cr
@@ -101,7 +101,7 @@ module HTTP
end
end
- def to_cookie_header(io)
+ def to_cookie_header(io) : Nil
io << @name
io << '='
io << @value
diff --git a/src/http/formdata/builder.cr b/src/http/formdata/builder.cr
index 983451f25865..17132bf0af72 100644
--- a/src/http/formdata/builder.cr
+++ b/src/http/formdata/builder.cr
@@ -39,7 +39,7 @@ module HTTP::FormData
# Adds a form part with the given *name* and *value*. *Headers* can
# optionally be provided for the form part.
- def field(name : String, value, headers : HTTP::Headers = HTTP::Headers.new)
+ def field(name : String, value, headers : HTTP::Headers = HTTP::Headers.new) : Nil
file(name, IO::Memory.new(value.to_s), headers: headers)
end
@@ -68,7 +68,7 @@ module HTTP::FormData
# Finalizes the multipart message, this method must be called before the
# generated multipart message written to the IO is considered valid.
- def finish
+ def finish : Nil
fail "Cannot finish form-data: no body parts" if @state == :START
fail "Cannot finish form-data: already finished" if @state == :FINISHED
diff --git a/src/http/server.cr b/src/http/server.cr
index 9ad6fc64ae4c..9a62c5f8b07a 100644
--- a/src/http/server.cr
+++ b/src/http/server.cr
@@ -480,7 +480,7 @@ class HTTP::Server
# Gracefully terminates the server. It will process currently accepted
# requests, but it won't accept new connections.
- def close
+ def close : Nil
raise "Can't close server, it's already closed" if closed?
@closed = true
diff --git a/src/http/server/handlers/error_handler.cr b/src/http/server/handlers/error_handler.cr
index c70f210bf925..9468917674ee 100644
--- a/src/http/server/handlers/error_handler.cr
+++ b/src/http/server/handlers/error_handler.cr
@@ -12,7 +12,7 @@ class HTTP::ErrorHandler
def initialize(@verbose : Bool = false, @log = Log.for("http.server"))
end
- def call(context)
+ def call(context) : Nil
begin
call_next(context)
rescue ex : HTTP::Server::ClientError
diff --git a/src/http/server/handlers/log_handler.cr b/src/http/server/handlers/log_handler.cr
index 36d1c90c7a91..8177ede9c712 100644
--- a/src/http/server/handlers/log_handler.cr
+++ b/src/http/server/handlers/log_handler.cr
@@ -8,7 +8,7 @@ class HTTP::LogHandler
def initialize(@log = Log.for("http.server"))
end
- def call(context)
+ def call(context) : Nil
start = Time.monotonic
begin
diff --git a/src/http/server/handlers/static_file_handler.cr b/src/http/server/handlers/static_file_handler.cr
index e70b694cb34f..02e782df2968 100644
--- a/src/http/server/handlers/static_file_handler.cr
+++ b/src/http/server/handlers/static_file_handler.cr
@@ -28,7 +28,7 @@ class HTTP::StaticFileHandler
@directory_listing = !!directory_listing
end
- def call(context)
+ def call(context) : Nil
unless context.request.method.in?("GET", "HEAD")
if @fallthrough
call_next(context)
diff --git a/src/http/server/handlers/websocket_handler.cr b/src/http/server/handlers/websocket_handler.cr
index e3a8b64ba3a2..1d9a20ccd354 100644
--- a/src/http/server/handlers/websocket_handler.cr
+++ b/src/http/server/handlers/websocket_handler.cr
@@ -20,7 +20,7 @@ class HTTP::WebSocketHandler
def initialize(&@proc : WebSocket, Server::Context ->)
end
- def call(context)
+ def call(context) : Nil
unless websocket_upgrade_request? context.request
return call_next context
end
diff --git a/src/http/server/request_processor.cr b/src/http/server/request_processor.cr
index 7b72dd3db744..0ecb2e1ccd9c 100644
--- a/src/http/server/request_processor.cr
+++ b/src/http/server/request_processor.cr
@@ -18,7 +18,7 @@ class HTTP::Server::RequestProcessor
@wants_close = false
end
- def close
+ def close : Nil
@wants_close = true
end
diff --git a/src/http/server/response.cr b/src/http/server/response.cr
index df15aaaa9a6b..1df0d9bfb73d 100644
--- a/src/http/server/response.cr
+++ b/src/http/server/response.cr
@@ -98,19 +98,19 @@ class HTTP::Server
# Upgrades this response, writing headers and yielding the connection `IO` (a socket) to the given block.
# This is useful to implement protocol upgrades, such as websockets.
- def upgrade(&block : IO ->)
+ def upgrade(&block : IO ->) : Nil
write_headers
@upgrade_handler = block
end
# Flushes the output. This method must be implemented if wrapping the response output.
- def flush
+ def flush : Nil
@output.flush
end
# Closes this response, writing headers and body if not done yet.
# This method must be implemented if wrapping the response output.
- def close
+ def close : Nil
return if closed?
@output.close
@@ -134,7 +134,7 @@ class HTTP::Server
#
# Raises `IO::Error` if the response is closed or headers were already
# sent.
- def respond_with_status(status : HTTP::Status, message : String? = nil)
+ def respond_with_status(status : HTTP::Status, message : String? = nil) : Nil
check_headers
reset
@status = status
@@ -145,7 +145,7 @@ class HTTP::Server
end
# :ditto:
- def respond_with_status(status : Int, message : String? = nil)
+ def respond_with_status(status : Int, message : String? = nil) : Nil
respond_with_status(HTTP::Status.new(status), message)
end
@@ -189,7 +189,7 @@ class HTTP::Server
@closed = false
end
- def reset
+ def reset : Nil
@in_buffer_rem = Bytes.empty
@out_count = 0
@sync = false
@@ -231,7 +231,7 @@ class HTTP::Server
@closed
end
- def close
+ def close : Nil
return if closed?
# Conditionally determine based on status if the `content-length` header should be added automatically.
diff --git a/src/http/web_socket.cr b/src/http/web_socket.cr
index 5a79e56190ba..6516dee2bbb8 100644
--- a/src/http/web_socket.cr
+++ b/src/http/web_socket.cr
@@ -30,6 +30,8 @@ class HTTP::WebSocket
# HTTP::WebSocket.new(URI.parse("http://websocket.example.com:8080/chat")) # Creates a new WebSocket to `websocket.example.com` on port `8080`
# HTTP::WebSocket.new(URI.parse("ws://websocket.example.com/chat"), # Creates a new WebSocket to `websocket.example.com` with an Authorization header
# HTTP::Headers{"Authorization" => "Bearer authtoken"})
+ # HTTP::WebSocket.new(
+ # URI.parse("ws://user:password@websocket.example.com/chat")) # Creates a new WebSocket to `websocket.example.com` with an HTTP basic auth Authorization header
# ```
def self.new(uri : URI | String, headers = HTTP::Headers.new)
new(Protocol.new(uri, headers: headers))
@@ -73,7 +75,7 @@ class HTTP::WebSocket
end
# Sends a message payload (message) to the client.
- def send(message)
+ def send(message) : Nil
check_open
@ws.send(message)
end
@@ -91,7 +93,7 @@ class HTTP::WebSocket
# Server can send an unsolicited PONG frame which the client should not respond to.
#
# See `#ping`.
- def pong(message = nil)
+ def pong(message = nil) : Nil
check_open
@ws.pong(message)
end
@@ -105,7 +107,7 @@ class HTTP::WebSocket
# Sends a close frame to the client, and closes the connection.
# The close frame may contain a body (message) that indicates the reason for closing.
- def close(code : CloseCode | Int? = nil, message = nil)
+ def close(code : CloseCode | Int? = nil, message = nil) : Nil
return if closed?
@closed = true
@ws.close(code, message)
diff --git a/src/http/web_socket/protocol.cr b/src/http/web_socket/protocol.cr
index 4c07889e0327..8aec99bfd148 100644
--- a/src/http/web_socket/protocol.cr
+++ b/src/http/web_socket/protocol.cr
@@ -74,7 +74,7 @@ class HTTP::WebSocket::Protocol
raise "This IO is write-only"
end
- def flush(final = true)
+ def flush(final = true) : Nil
@websocket.send(
@buffer + (@pos % @buffer.size),
@opcode,
@@ -86,11 +86,11 @@ class HTTP::WebSocket::Protocol
end
end
- def send(data : String)
+ def send(data : String) : Nil
send(data.to_slice, Opcode::TEXT)
end
- def send(data : Bytes)
+ def send(data : Bytes) : Nil
send(data, Opcode::BINARY)
end
@@ -100,7 +100,7 @@ class HTTP::WebSocket::Protocol
stream_io.flush
end
- def send(data : Bytes, opcode : Opcode, flags = Flags::FINAL, flush = true)
+ def send(data : Bytes, opcode : Opcode, flags = Flags::FINAL, flush = true) : Nil
write_header(data.size, opcode, flags)
write_payload(data)
@io.flush if flush
@@ -227,7 +227,7 @@ class HTTP::WebSocket::Protocol
end
end
- def pong(message = nil)
+ def pong(message = nil) : Nil
if message
send(message.to_slice, Opcode::PONG)
else
@@ -235,7 +235,7 @@ class HTTP::WebSocket::Protocol
end
end
- def close(code : CloseCode? = nil, message = nil)
+ def close(code : CloseCode? = nil, message = nil) : Nil
return if @io.closed?
if message
@@ -259,7 +259,7 @@ class HTTP::WebSocket::Protocol
@io.close if @sync_close
end
- def close(code : Int, message = nil)
+ def close(code : Int, message = nil) : Nil
close(CloseCode.new(code), message)
end
@@ -320,6 +320,9 @@ class HTTP::WebSocket::Protocol
if (host = uri.hostname) && (path = uri.request_target)
tls = uri.scheme.in?("https", "wss")
+ if (user = uri.user) && (password = uri.password)
+ headers["Authorization"] ||= "Basic #{Base64.strict_encode("#{user}:#{password}")}"
+ end
return new(host, path, uri.port, tls, headers)
end
diff --git a/src/int.cr b/src/int.cr
index 57114be4e52b..13c455f1a25f 100644
--- a/src/int.cr
+++ b/src/int.cr
@@ -616,41 +616,115 @@ struct Int
private DIGITS_UPCASE = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
private DIGITS_BASE62 = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
- def to_s(base : Int = 10, *, upcase : Bool = false) : String
+ # Returns a string representation of this integer.
+ #
+ # *base* specifies the radix of the returned string, and must be either 62 or
+ # a number between 2 and 36. By default, digits above 9 are represented by
+ # ASCII lowercase letters (`a` for 10, `b` for 11, etc.), but uppercase
+ # letters may be used if *upcase* is `true`, unless base 62 is used. In that
+ # case, lowercase letters are used for 10 to 35, and uppercase ones for 36 to
+ # 61, and *upcase* must be `false`.
+ #
+ # *precision* specifies the minimum number of digits in the returned string.
+ # If there are fewer digits than this number, the string is left-padded by
+ # zeros. If `self` and *precision* are both zero, returns an empty string.
+ #
+ # ```
+ # 1234.to_s # => "1234"
+ # 1234.to_s(2) # => "10011010010"
+ # 1234.to_s(16) # => "4d2"
+ # 1234.to_s(16, upcase: true) # => "4D2"
+ # 1234.to_s(36) # => "ya"
+ # 1234.to_s(62) # => "jU"
+ # 1234.to_s(precision: 2) # => "1234"
+ # 1234.to_s(precision: 6) # => "001234"
+ # ```
+ def to_s(base : Int = 10, *, precision : Int = 1, upcase : Bool = false) : String
raise ArgumentError.new("Invalid base #{base}") unless 2 <= base <= 36 || base == 62
raise ArgumentError.new("upcase must be false for base 62") if upcase && base == 62
+ raise ArgumentError.new("Precision must be non-negative") unless precision >= 0
- case self
- when 0
+ case {self, precision}
+ when {0, 0}
+ ""
+ when {0, 1}
"0"
- when 1
+ when {1, 1}
"1"
else
- internal_to_s(base, upcase) do |ptr, count|
- String.new(ptr, count, count)
+ internal_to_s(base, precision, upcase) do |ptr, count, negative|
+ # reuse the `chars` buffer in `internal_to_s` if possible
+ if precision <= count || precision <= 128
+ if precision > count
+ difference = precision - count
+ ptr -= difference
+ Intrinsics.memset(ptr, '0'.ord.to_u8, difference, false)
+ count += difference
+ end
+
+ if negative
+ ptr -= 1
+ ptr.value = '-'.ord.to_u8
+ count += 1
+ end
+
+ String.new(ptr, count, count)
+ else
+ len = precision + (negative ? 1 : 0)
+ String.new(len) do |buffer|
+ if negative
+ buffer.value = '-'.ord.to_u8
+ buffer += 1
+ end
+
+ Intrinsics.memset(buffer, '0'.ord.to_u8, precision - count, false)
+ ptr.copy_to(buffer + precision - count, count)
+ {len, len}
+ end
+ end
end
end
end
- def to_s(io : IO, base : Int = 10, *, upcase : Bool = false) : Nil
+ # Appends a string representation of this integer to the given *io*.
+ #
+ # *base* specifies the radix of the written string, and must be either 62 or
+ # a number between 2 and 36. By default, digits above 9 are represented by
+ # ASCII lowercase letters (`a` for 10, `b` for 11, etc.), but uppercase
+ # letters may be used if *upcase* is `true`, unless base 62 is used. In that
+ # case, lowercase letters are used for 10 to 35, and uppercase ones for 36 to
+ # 61, and *upcase* must be `false`.
+ #
+ # *precision* specifies the minimum number of digits in the written string.
+ # If there are fewer digits than this number, the string is left-padded by
+ # zeros. If `self` and *precision* are both zero, returns an empty string.
+ def to_s(io : IO, base : Int = 10, *, precision : Int = 1, upcase : Bool = false) : Nil
raise ArgumentError.new("Invalid base #{base}") unless 2 <= base <= 36 || base == 62
raise ArgumentError.new("upcase must be false for base 62") if upcase && base == 62
+ raise ArgumentError.new("Precision must be non-negative") unless precision >= 0
- case self
- when 0
+ case {self, precision}
+ when {0, 0}
+ # do nothing
+ when {0, 1}
io << '0'
- when 1
+ when {1, 1}
io << '1'
else
- internal_to_s(base, upcase) do |ptr, count|
- io.write_utf8 Slice.new(ptr, count)
+ internal_to_s(base, precision, upcase) do |ptr, count, negative|
+ io << '-' if negative
+ if precision > count
+ (precision - count).times { io << '0' }
+ end
+ io.write_string Slice.new(ptr, count)
end
end
end
- private def internal_to_s(base, upcase = false)
+ private def internal_to_s(base, precision, upcase = false)
# Given sizeof(self) <= 128 bits, we need at most 128 bytes for a base 2
- # representation, plus one byte for the trailing 0.
+ # representation, plus one byte for the negative sign (possibly used by the
+ # string-returning overload).
chars = uninitialized UInt8[129]
ptr_end = chars.to_unsafe + 128
ptr = ptr_end
@@ -666,13 +740,8 @@ struct Int
num = num.tdiv(base)
end
- if neg
- ptr -= 1
- ptr.value = '-'.ord.to_u8
- end
-
count = (ptr_end - ptr).to_i32
- yield ptr, count
+ yield ptr, count, neg
end
# Writes this integer to the given *io* in the given *format*.
diff --git a/src/io.cr b/src/io.cr
index 5e941ced23db..58da0a7b6907 100644
--- a/src/io.cr
+++ b/src/io.cr
@@ -463,8 +463,21 @@ abstract class IO
nil
end
- # Writes a slice of UTF-8 encoded bytes to this `IO`, using the current encoding.
- def write_utf8(slice : Bytes) : Nil
+ # Writes the contents of *slice*, interpreted as a sequence of UTF-8 or ASCII
+ # characters, into this `IO`. The contents are transcoded into this `IO`'s
+ # current encoding.
+ #
+ # ```
+ # bytes = "你".to_slice # => Bytes[228, 189, 160]
+ #
+ # io = IO::Memory.new
+ # io.set_encoding("GB2312")
+ # io.write_string(bytes)
+ # io.to_slice # => Bytes[196, 227]
+ #
+ # "你".encode("GB2312") # => Bytes[196, 227]
+ # ```
+ def write_string(slice : Bytes) : Nil
if encoder = encoder()
encoder.write(self, slice)
else
@@ -474,6 +487,12 @@ abstract class IO
nil
end
+ # :ditto:
+ @[Deprecated("Use `#write_string` instead.")]
+ def write_utf8(slice : Bytes) : Nil
+ write_string(slice)
+ end
+
private def encoder
if encoding = @encoding
@encoder ||= Encoder.new(encoding)
@@ -1010,10 +1029,7 @@ abstract class IO
# String operations (`gets`, `gets_to_end`, `read_char`, `<<`, `print`, `puts`
# `printf`) will use this encoding.
def set_encoding(encoding : String, invalid : Symbol? = nil) : Nil
- if invalid != :skip && (
- encoding.compare("UTF-8", case_insensitive: true) == 0 ||
- encoding.compare("UTF8", case_insensitive: true) == 0
- )
+ if utf8_encoding?(encoding, invalid)
@encoding = nil
else
@encoding = EncodingOptions.new(encoding, invalid)
@@ -1030,6 +1046,13 @@ abstract class IO
@encoding.try(&.name) || "UTF-8"
end
+ private def utf8_encoding?(encoding : String, invalid : Symbol? = nil) : Bool
+ invalid.nil? && (
+ encoding.compare("UTF-8", case_insensitive: true) == 0 ||
+ encoding.compare("UTF8", case_insensitive: true) == 0
+ )
+ end
+
# :nodoc:
def has_non_utf8_encoding? : Bool
!!@encoding
diff --git a/src/io/console.cr b/src/io/console.cr
index 849a82af2c97..a5ea2e52530c 100644
--- a/src/io/console.cr
+++ b/src/io/console.cr
@@ -51,7 +51,7 @@ class IO::FileDescriptor < IO
# doing line wise editing by the terminal and only sending the input to
# the program on a newline.
# Only call this when this IO is a TTY, such as a not redirected stdin.
- def cooked!
+ def cooked! : Nil
if LibC.tcgetattr(fd, out mode) != 0
raise IO::Error.from_errno "can't set IO#cooked!"
end
diff --git a/src/io/delimited.cr b/src/io/delimited.cr
index 172362309346..3451b47f4492 100644
--- a/src/io/delimited.cr
+++ b/src/io/delimited.cr
@@ -111,7 +111,7 @@ class IO::Delimited < IO
raise IO::Error.new "Can't write to IO::Delimited"
end
- def close
+ def close : Nil
return if @closed
@closed = true
diff --git a/src/io/encoding.cr b/src/io/encoding.cr
index 48a2023cdcd3..665b3e3fcfa1 100644
--- a/src/io/encoding.cr
+++ b/src/io/encoding.cr
@@ -224,17 +224,17 @@ class IO
string
end
- def write(io)
+ def write(io) : Nil
io.write @out_slice
@out_slice = Bytes.empty
end
- def write(io, numbytes)
+ def write(io, numbytes) : Nil
io.write @out_slice[0, numbytes]
@out_slice += numbytes
end
- def advance(numbytes)
+ def advance(numbytes) : Nil
@out_slice += numbytes
end
diff --git a/src/io/evented.cr b/src/io/evented.cr
index 75f60b76145a..c2f12bfebff1 100644
--- a/src/io/evented.cr
+++ b/src/io/evented.cr
@@ -165,11 +165,11 @@ module IO::Evented
event.add timeout
end
- def evented_reopen
+ def evented_reopen : Nil
evented_close
end
- def evented_close
+ def evented_close : Nil
@read_event.consume_each &.free
@write_event.consume_each &.free
diff --git a/src/io/memory.cr b/src/io/memory.cr
index 1c1583255183..7d5503024557 100644
--- a/src/io/memory.cr
+++ b/src/io/memory.cr
@@ -242,7 +242,7 @@ class IO::Memory < IO
# io = IO::Memory.new "hello"
# io.clear # raises IO::Error
# ```
- def clear
+ def clear : Nil
check_open
check_resizeable
@bytesize = 0
@@ -372,7 +372,7 @@ class IO::Memory < IO
# io.close
# io.gets_to_end # raises IO::Error (closed stream)
# ```
- def close
+ def close : Nil
@closed = true
end
diff --git a/src/io/sized.cr b/src/io/sized.cr
index ad451523fffe..d46a548da432 100644
--- a/src/io/sized.cr
+++ b/src/io/sized.cr
@@ -76,7 +76,7 @@ class IO::Sized < IO
raise IO::Error.new "Can't write to IO::Sized"
end
- def close
+ def close : Nil
return if @closed
@closed = true
diff --git a/src/io/stapled.cr b/src/io/stapled.cr
index 98d90eadb238..774873476fb6 100644
--- a/src/io/stapled.cr
+++ b/src/io/stapled.cr
@@ -65,7 +65,7 @@ class IO::Stapled < IO
end
# Writes a byte to `writer`.
- def write_byte(byte : UInt8)
+ def write_byte(byte : UInt8) : Nil
check_open
@writer.write_byte(byte)
diff --git a/src/iterator.cr b/src/iterator.cr
index 179e5aac0493..4f3373c30d29 100644
--- a/src/iterator.cr
+++ b/src/iterator.cr
@@ -590,7 +590,7 @@ module Iterator(T)
# iter = ["a", "b", "c"].each
# iter.each { |x| print x, " " } # Prints "a b c"
# ```
- def each(& : T -> _) : Nil
+ def each(& : T ->) : Nil
while true
value = self.next
break if value.is_a?(Stop)
diff --git a/src/json/builder.cr b/src/json/builder.cr
index 49af4b7bdb6d..65b10fc03c10 100644
--- a/src/json/builder.cr
+++ b/src/json/builder.cr
@@ -28,7 +28,7 @@ class JSON::Builder
end
# Starts a document.
- def start_document
+ def start_document : Nil
case state = @state.last
when StartState
@state[-1] = DocumentStartState.new
@@ -62,28 +62,28 @@ class JSON::Builder
end
# Writes a `null` value.
- def null
+ def null : Nil
scalar do
@io << "null"
end
end
# Writes a boolean value.
- def bool(value : Bool)
+ def bool(value : Bool) : Nil
scalar do
@io << value
end
end
# Writes an integer.
- def number(number : Int)
+ def number(number : Int) : Nil
scalar do
@io << number
end
end
# Writes a float.
- def number(number : Float)
+ def number(number : Float) : Nil
scalar do
case number
when .nan?
@@ -100,7 +100,7 @@ class JSON::Builder
# by invoking `to_s` on it.
#
# This method can also be used to write the name of an object field.
- def string(value)
+ def string(value) : Nil
string = value.to_s
scalar(string: true) do
@@ -157,14 +157,14 @@ class JSON::Builder
# the IO without processing. This is the only method that
# might lead to invalid JSON being generated, so you must
# be sure that *string* contains a valid JSON string.
- def raw(string : String)
+ def raw(string : String) : Nil
scalar do
@io << string
end
end
# Writes the start of an array.
- def start_array
+ def start_array : Nil
start_scalar
increase_indent
@state.push ArrayState.new(empty: true)
@@ -172,7 +172,7 @@ class JSON::Builder
end
# Writes the end of an array.
- def end_array
+ def end_array : Nil
case state = @state.last
when ArrayState
@state.pop
@@ -193,7 +193,7 @@ class JSON::Builder
end
# Writes the start of an object.
- def start_object
+ def start_object : Nil
start_scalar
increase_indent
@state.push ObjectState.new(empty: true, name: true)
@@ -201,7 +201,7 @@ class JSON::Builder
end
# Writes the end of an object.
- def end_object
+ def end_object : Nil
case state = @state.last
when ObjectState
unless state.name
@@ -235,12 +235,12 @@ class JSON::Builder
end
# :ditto:
- def scalar(value : Int | Float)
+ def scalar(value : Int | Float) : Nil
number(value)
end
# :ditto:
- def scalar(value : String)
+ def scalar(value : String) : Nil
string(value)
end
diff --git a/src/json/pull_parser.cr b/src/json/pull_parser.cr
index c8fe81538201..7258d6d958f0 100644
--- a/src/json/pull_parser.cr
+++ b/src/json/pull_parser.cr
@@ -103,30 +103,32 @@ class JSON::PullParser
next_token
case token.kind
- when .null?
+ in .null?
@kind = :null
- when .false?
+ in .false?
@kind = :bool
@bool_value = false
- when .true?
+ in .true?
@kind = :bool
@bool_value = true
- when .int?
+ in .int?
@kind = :int
@int_value = token.int_value
@raw_value = token.raw_value
- when .float?
+ in .float?
@kind = :float
@float_value = token.float_value
@raw_value = token.raw_value
- when .string?
+ in .string?
@kind = :string
@string_value = token.string_value
- when .begin_array?
+ in .begin_array?
begin_array
- when .begin_object?
+ in .begin_object?
begin_object
- else
+ in .eof?
+ @kind = :eof
+ in .end_array?, .end_object?, .comma?, .colon?
unexpected_token
end
end
@@ -263,7 +265,7 @@ class JSON::PullParser
# Reads the new value and fill the a JSON builder with it.
#
# Use this method with a `JSON::Builder` to read a JSON while building another one.
- def read_raw(json)
+ def read_raw(json) : Nil
case @kind
when .null?
read_next
@@ -556,7 +558,7 @@ class JSON::PullParser
#
# It skips the whole value, not only the next lexer's token.
# For example if the next value is an array, the whole array will be skipped.
- def skip
+ def skip : Nil
@lexer.skip = true
skip_internal
@lexer.skip = false
diff --git a/src/json/to_json.cr b/src/json/to_json.cr
index 0c0bcf482367..2b3224529e84 100644
--- a/src/json/to_json.cr
+++ b/src/json/to_json.cr
@@ -25,7 +25,7 @@ class Object
end
struct Nil
- def to_json(json : JSON::Builder)
+ def to_json(json : JSON::Builder) : Nil
json.null
end
@@ -35,13 +35,13 @@ struct Nil
end
struct Bool
- def to_json(json : JSON::Builder)
+ def to_json(json : JSON::Builder) : Nil
json.bool(self)
end
end
struct Int
- def to_json(json : JSON::Builder)
+ def to_json(json : JSON::Builder) : Nil
json.number(self)
end
@@ -51,7 +51,7 @@ struct Int
end
struct Float
- def to_json(json : JSON::Builder)
+ def to_json(json : JSON::Builder) : Nil
json.number(self)
end
@@ -61,7 +61,7 @@ struct Float
end
class String
- def to_json(json : JSON::Builder)
+ def to_json(json : JSON::Builder) : Nil
json.string(self)
end
@@ -71,7 +71,7 @@ class String
end
struct Path
- def to_json(json : JSON::Builder)
+ def to_json(json : JSON::Builder) : Nil
@name.to_json(json)
end
@@ -81,7 +81,7 @@ struct Path
end
struct Symbol
- def to_json(json : JSON::Builder)
+ def to_json(json : JSON::Builder) : Nil
json.string(to_s)
end
@@ -91,7 +91,7 @@ struct Symbol
end
class Array
- def to_json(json : JSON::Builder)
+ def to_json(json : JSON::Builder) : Nil
json.array do
each &.to_json(json)
end
@@ -99,7 +99,7 @@ class Array
end
class Deque
- def to_json(json : JSON::Builder)
+ def to_json(json : JSON::Builder) : Nil
json.array do
each &.to_json(json)
end
@@ -107,7 +107,7 @@ class Deque
end
struct Set
- def to_json(json : JSON::Builder)
+ def to_json(json : JSON::Builder) : Nil
json.array do
each &.to_json(json)
end
@@ -120,7 +120,7 @@ class Hash
# Keys are serialized by invoking `to_json_object_key` on them.
# Values are serialized with the usual `to_json(json : JSON::Builder)`
# method.
- def to_json(json : JSON::Builder)
+ def to_json(json : JSON::Builder) : Nil
json.object do
each do |key, value|
json.field key.to_json_object_key do
@@ -132,7 +132,7 @@ class Hash
end
struct Tuple
- def to_json(json : JSON::Builder)
+ def to_json(json : JSON::Builder) : Nil
json.array do
{% for i in 0...T.size %}
self[{{i}}].to_json(json)
@@ -154,7 +154,7 @@ struct NamedTuple
end
struct Time::Format
- def to_json(value : Time, json : JSON::Builder)
+ def to_json(value : Time, json : JSON::Builder) : Nil
format(value).to_json(json)
end
end
@@ -263,7 +263,7 @@ struct Time
# a time value.
#
# See `#from_json` for reference.
- def to_json(json : JSON::Builder)
+ def to_json(json : JSON::Builder) : Nil
json.string(Time::Format::RFC_3339.format(self, fraction_digits: 0))
end
end
@@ -343,7 +343,7 @@ end
# person.to_json # => %({"birth_date":1459859781})
# ```
module Time::EpochConverter
- def self.to_json(value : Time, json : JSON::Builder)
+ def self.to_json(value : Time, json : JSON::Builder) : Nil
json.number(value.to_unix)
end
end
@@ -367,7 +367,7 @@ end
# timestamp.to_json # => %({"value":1459860483856})
# ```
module Time::EpochMillisConverter
- def self.to_json(value : Time, json : JSON::Builder)
+ def self.to_json(value : Time, json : JSON::Builder) : Nil
json.number(value.to_unix_ms)
end
end
@@ -394,7 +394,7 @@ end
# raw.to_json # => %({"value":123456789876543212345678987654321})
# ```
module String::RawConverter
- def self.to_json(value : String, json : JSON::Builder)
+ def self.to_json(value : String, json : JSON::Builder) : Nil
json.raw(value)
end
end
diff --git a/src/llvm/enums.cr b/src/llvm/enums.cr
index 275c8dd8f8a1..a604d1d7d7ce 100644
--- a/src/llvm/enums.cr
+++ b/src/llvm/enums.cr
@@ -60,6 +60,7 @@ module LLVM
ZExt
@@kind_ids = load_llvm_kinds_from_names.as(Hash(Attribute, UInt32))
+ @@typed_attrs = load_llvm_typed_attributes.as(Array(Attribute))
def each_kind(&block)
return if value == 0
@@ -137,6 +138,18 @@ module LLVM
kinds
end
+ private def self.load_llvm_typed_attributes
+ typed_attrs = [] of Attribute
+
+ unless LibLLVM::IS_LT_120
+ # LLVM 12 introduced mandatory type parameters for byval and sret
+ typed_attrs << ByVal
+ typed_attrs << StructRet
+ end
+
+ typed_attrs
+ end
+
def self.kind_for(member)
@@kind_ids[member]
end
@@ -144,6 +157,11 @@ module LLVM
def self.from_kind(kind)
@@kind_ids.key_for(kind)
end
+
+ def self.requires_type?(kind)
+ member = from_kind(kind)
+ @@typed_attrs.includes?(member)
+ end
end
{% else %}
@[Flags]
diff --git a/src/llvm/ext/llvm-versions.txt b/src/llvm/ext/llvm-versions.txt
index 2fdbdf9a1c98..7a8af33aa8b5 100644
--- a/src/llvm/ext/llvm-versions.txt
+++ b/src/llvm/ext/llvm-versions.txt
@@ -1 +1 @@
-11.1 11.0 10.0 9.0 8.0 7.1 6.0 5.0 4.0 3.9 3.8
+12.0 11.1 11.0 10.0 9.0 8.0 7.1 6.0 5.0 4.0 3.9 3.8
diff --git a/src/llvm/ext/llvm_ext.cc b/src/llvm/ext/llvm_ext.cc
index 4d2b13cf4193..bd82f564a903 100644
--- a/src/llvm/ext/llvm_ext.cc
+++ b/src/llvm/ext/llvm_ext.cc
@@ -351,11 +351,36 @@ void LLVMMetadataReplaceAllUsesWith2(
void LLVMExtSetCurrentDebugLocation(
LLVMBuilderRef Bref, unsigned Line, unsigned Col, LLVMMetadataRef Scope,
LLVMMetadataRef InlinedAt) {
+#if LLVM_VERSION_GE(12, 0)
+ if (!Scope)
+ unwrap(Bref)->SetCurrentDebugLocation(DebugLoc());
+ else
+ unwrap(Bref)->SetCurrentDebugLocation(
+ DILocation::get(unwrap(Scope)->getContext(), Line, Col,
+ unwrapDI(Scope),
+ unwrapDI(InlinedAt)));
+#else
unwrap(Bref)->SetCurrentDebugLocation(
DebugLoc::get(Line, Col, Scope ? unwrap(Scope) : nullptr,
InlinedAt ? unwrap(InlinedAt) : nullptr));
+#endif
}
+#if LLVM_VERSION_GE(3, 9)
+// A backported LLVMCreateTypeAttribute for LLVM < 13
+// from https://github.com/llvm/llvm-project/blob/bb8ce25e88218be60d2a4ea9c9b0b721809eff27/llvm/lib/IR/Core.cpp#L167
+LLVMAttributeRef LLVMExtCreateTypeAttribute(
+ LLVMContextRef C, unsigned KindID, LLVMTypeRef Ty) {
+ auto &Ctx = *unwrap(C);
+ auto AttrKind = (Attribute::AttrKind)KindID;
+#if LLVM_VERSION_GE(12, 0)
+ return wrap(Attribute::get(Ctx, AttrKind, unwrap(Ty)));
+#else
+ return wrap(Attribute::get(Ctx, AttrKind));
+#endif
+}
+#endif
+
LLVMValueRef LLVMExtBuildCmpxchg(
LLVMBuilderRef B, LLVMValueRef PTR, LLVMValueRef Cmp, LLVMValueRef New,
LLVMAtomicOrdering SuccessOrdering, LLVMAtomicOrdering FailureOrdering) {
diff --git a/src/llvm/function.cr b/src/llvm/function.cr
index 7ffa10cb688e..13d6af5ca5cd 100644
--- a/src/llvm/function.cr
+++ b/src/llvm/function.cr
@@ -19,12 +19,16 @@ struct LLVM::Function
LibLLVM.set_function_call_convention(self, cc)
end
- def add_attribute(attribute : Attribute, index = AttributeIndex::FunctionIndex)
+ def add_attribute(attribute : Attribute, index = AttributeIndex::FunctionIndex, type : Type? = nil)
return if attribute.value == 0
{% if LibLLVM.has_constant?(:AttributeRef) %}
context = LibLLVM.get_module_context(LibLLVM.get_global_parent(self))
attribute.each_kind do |kind|
- attribute_ref = LibLLVM.create_enum_attribute(context, kind, 0)
+ if type && LLVM::Attribute.requires_type?(kind)
+ attribute_ref = LibLLVMExt.create_type_attribute(context, kind, type)
+ else
+ attribute_ref = LibLLVM.create_enum_attribute(context, kind, 0)
+ end
LibLLVM.add_attribute_at_index(self, index, attribute_ref)
end
{% else %}
diff --git a/src/llvm/lib_llvm.cr b/src/llvm/lib_llvm.cr
index 857987d0a36e..694d1767a63a 100644
--- a/src/llvm/lib_llvm.cr
+++ b/src/llvm/lib_llvm.cr
@@ -19,6 +19,7 @@ end
{% begin %}
lib LibLLVM
+ IS_120 = {{LibLLVM::VERSION.starts_with?("12.0")}}
IS_110 = {{LibLLVM::VERSION.starts_with?("11.0")}}
IS_100 = {{LibLLVM::VERSION.starts_with?("10.0")}}
IS_90 = {{LibLLVM::VERSION.starts_with?("9.0")}}
@@ -33,7 +34,10 @@ end
IS_LT_70 = IS_38 || IS_39 || IS_40 || IS_50 || IS_60
IS_LT_80 = IS_LT_70 || IS_70 || IS_71
- IS_LT_110 = IS_LT_80 || IS_90 || IS_100
+ IS_LT_90 = IS_LT_80 || IS_80
+ IS_LT_100 = IS_LT_90 || IS_90
+ IS_LT_110 = IS_LT_100 || IS_100
+ IS_LT_120 = IS_LT_110 || IS_110
end
{% end %}
diff --git a/src/llvm/lib_llvm_ext.cr b/src/llvm/lib_llvm_ext.cr
index a9d0dd9c7eed..c14e6f3ebc97 100644
--- a/src/llvm/lib_llvm_ext.cr
+++ b/src/llvm/lib_llvm_ext.cr
@@ -165,4 +165,9 @@ lib LibLLVMExt
fun target_machine_enable_global_isel = LLVMExtTargetMachineEnableGlobalIsel(machine : LibLLVM::TargetMachineRef, enable : Bool)
fun create_mc_jit_compiler_for_module = LLVMExtCreateMCJITCompilerForModule(jit : LibLLVM::ExecutionEngineRef*, m : LibLLVM::ModuleRef, options : LibLLVM::JITCompilerOptions*, options_length : UInt32, enable_global_isel : Bool, error : UInt8**) : Int32
+
+ {% unless LibLLVM::IS_38 %}
+ # LLVMCreateTypeAttribute is implemented in LLVM 13, but needed in 12
+ fun create_type_attribute = LLVMExtCreateTypeAttribute(ctx : LibLLVM::ContextRef, kind_id : LibC::UInt, ty : LibLLVM::TypeRef) : LibLLVM::AttributeRef
+ {% end %}
end
diff --git a/src/llvm/value_methods.cr b/src/llvm/value_methods.cr
index bab4569638ba..30a2e2bb7df1 100644
--- a/src/llvm/value_methods.cr
+++ b/src/llvm/value_methods.cr
@@ -14,11 +14,16 @@ module LLVM::ValueMethods
LibLLVM.get_value_kind(self)
end
- def add_instruction_attribute(index : Int, attribute : LLVM::Attribute, context : LLVM::Context)
+ def add_instruction_attribute(index : Int, attribute : LLVM::Attribute, context : LLVM::Context, type : LLVM::Type? = nil)
return if attribute.value == 0
{% if LibLLVM.has_constant?(:AttributeRef) %}
attribute.each_kind do |kind|
- attribute_ref = LibLLVM.create_enum_attribute(context, kind, 0)
+ if type && LLVM::Attribute.requires_type?(kind)
+ attribute_ref = LibLLVMExt.create_type_attribute(context, kind, type)
+ else
+ attribute_ref = LibLLVM.create_enum_attribute(context, kind, 0)
+ end
+
LibLLVM.add_call_site_attribute(self, index, attribute_ref)
end
{% else %}
diff --git a/src/log/broadcast_backend.cr b/src/log/broadcast_backend.cr
index 8a942baa374f..b6b558164020 100644
--- a/src/log/broadcast_backend.cr
+++ b/src/log/broadcast_backend.cr
@@ -15,7 +15,7 @@ class Log::BroadcastBackend < Log::Backend
super(:direct)
end
- def append(backend : Log::Backend, level : Severity)
+ def append(backend : Log::Backend, level : Severity) : Nil
@backends[backend] = level
end
@@ -47,7 +47,7 @@ class Log::BroadcastBackend < Log::Backend
end
# :nodoc:
- def remove(backend : Log::Backend)
+ def remove(backend : Log::Backend) : Nil
@backends.delete(backend)
end
end
diff --git a/src/log/dispatch.cr b/src/log/dispatch.cr
index e62e548bafb1..ba7cd1b00f81 100644
--- a/src/log/dispatch.cr
+++ b/src/log/dispatch.cr
@@ -51,7 +51,7 @@ class Log
spawn write_logs
end
- def dispatch(entry : Entry, backend : Backend)
+ def dispatch(entry : Entry, backend : Backend) : Nil
@channel.send({entry, backend})
end
diff --git a/src/log/format.cr b/src/log/format.cr
index 0baebcf26f6a..7a26699d68d7 100644
--- a/src/log/format.cr
+++ b/src/log/format.cr
@@ -64,17 +64,17 @@ class Log
end
# Write the entry timestamp in RFC3339 format
- def timestamp
+ def timestamp : Nil
@entry.timestamp.to_rfc3339(@io, fraction_digits: 6)
end
# Write a fixed string
- def string(str)
+ def string(str) : Nil
@io << str
end
# Write the message of the entry
- def message
+ def message : Nil
@io << @entry.message
end
@@ -107,7 +107,7 @@ class Log
# It doesn't write any output if the entry data is empty.
# Parameters `before` and `after` can be provided to be written around
# the value.
- def data(*, before = nil, after = nil)
+ def data(*, before = nil, after = nil) : Nil
unless @entry.data.empty?
@io << before << @entry.data << after
end
@@ -130,7 +130,7 @@ class Log
# Parameters `before` and `after` can be provided to be written around
# the value. `before` defaults to `'\n'` so the exception is written
# on a separate line
- def exception(*, before = '\n', after = nil)
+ def exception(*, before = '\n', after = nil) : Nil
if ex = @entry.exception
@io << before
ex.inspect_with_backtrace(@io)
@@ -139,7 +139,7 @@ class Log
end
# Write the program name. See `Log.progname`.
- def progname
+ def progname : Nil
@io << Log.progname
end
@@ -149,7 +149,7 @@ class Log
end
# Write the `Log::Entry` to the `IO` using this pattern
- def self.format(entry, io)
+ def self.format(entry, io) : Nil
new(entry, io).run
end
diff --git a/src/log/io_backend.cr b/src/log/io_backend.cr
index b449444ba439..cc7e38458552 100644
--- a/src/log/io_backend.cr
+++ b/src/log/io_backend.cr
@@ -14,7 +14,7 @@ class Log::IOBackend < Log::Backend
end
{% end %}
- def write(entry : Entry)
+ def write(entry : Entry) : Nil
format(entry)
io.puts
io.flush
@@ -22,7 +22,7 @@ class Log::IOBackend < Log::Backend
# Emits the *entry* to the given *io*.
# It uses the `#formatter` to convert.
- def format(entry : Entry)
+ def format(entry : Entry) : Nil
@formatter.format(entry, io)
end
end
diff --git a/src/log/main.cr b/src/log/main.cr
index e3ea3ba65412..bfa27b870151 100644
--- a/src/log/main.cr
+++ b/src/log/main.cr
@@ -120,7 +120,7 @@ class Log
# Log.context.clear
# Log.info { "message with empty context" }
# ```
- def clear
+ def clear : Nil
Fiber.current.logging_context = @metadata = Log::Metadata.empty
end
@@ -142,7 +142,7 @@ class Log
end
# :ditto:
- def set(values)
+ def set(values) : Nil
extend_fiber_context(Fiber.current, values)
end
diff --git a/src/log/memory_backend.cr b/src/log/memory_backend.cr
index 483f9aae0e51..22f979fda566 100644
--- a/src/log/memory_backend.cr
+++ b/src/log/memory_backend.cr
@@ -7,7 +7,7 @@ class Log::MemoryBackend < Log::Backend
super(:direct)
end
- def write(entry : Log::Entry)
+ def write(entry : Log::Entry) : Nil
@entries << entry
end
end
diff --git a/src/math/libm.cr b/src/math/libm.cr
index 1840108f8d42..844e59f10528 100644
--- a/src/math/libm.cr
+++ b/src/math/libm.cr
@@ -94,6 +94,8 @@ lib LibM
fun log1p_f64 = log1p(value : Float64) : Float64
fun logb_f32 = logbf(value : Float32) : Float32
fun logb_f64 = logb(value : Float64) : Float64
+ fun nextafter_f32 = nextafterf(from : Float32, to : Float32) : Float32
+ fun nextafter_f64 = nextafter(from : Float64, to : Float64) : Float64
fun scalbln_f32 = scalblnf(value1 : Float32, value2 : Int64) : Float32
fun scalbln_f64 = scalbln(value1 : Float64, value2 : Int64) : Float64
fun scalbn_f32 = scalbnf(value1 : Float32, value2 : Int32) : Float32
diff --git a/src/mime/multipart/builder.cr b/src/mime/multipart/builder.cr
index e96d6fe121b1..416fc3aa5ded 100644
--- a/src/mime/multipart/builder.cr
+++ b/src/mime/multipart/builder.cr
@@ -41,7 +41,7 @@ module MIME::Multipart
# if `#body_part` is called before this method.
#
# Can be called multiple times to append to the preamble multiple times.
- def preamble(string : String)
+ def preamble(string : String) : Nil
preamble { |io| string.to_s(io) }
end
@@ -49,7 +49,7 @@ module MIME::Multipart
# if `#body_part` is called before this method.
#
# Can be called multiple times to append to the preamble multiple times.
- def preamble(data : Bytes)
+ def preamble(data : Bytes) : Nil
preamble { |io| io.write data }
end
@@ -57,7 +57,7 @@ module MIME::Multipart
# Throws if `#body_part` is called before this method.
#
# Can be called multiple times to append to the preamble multiple times.
- def preamble(preamble_io : IO)
+ def preamble(preamble_io : IO) : Nil
preamble { |io| IO.copy(preamble_io, io) }
end
@@ -74,21 +74,21 @@ module MIME::Multipart
# Appends a body part to the multipart message with the given *headers*
# and *string*. Throws if `#finish` or `#epilogue` is called before this
# method.
- def body_part(headers : HTTP::Headers, string : String)
+ def body_part(headers : HTTP::Headers, string : String) : Nil
body_part_impl(headers) { |io| string.to_s(io) }
end
# Appends a body part to the multipart message with the given *headers*
# and *data*. Throws if `#finish` or `#epilogue` is called before this
# method.
- def body_part(headers : HTTP::Headers, data : Bytes)
+ def body_part(headers : HTTP::Headers, data : Bytes) : Nil
body_part_impl(headers) { |io| io.write data }
end
# Appends a body part to the multipart message with the given *headers*
# and data from *body_io*. Throws if `#finish` or `#epilogue` is called
# before this method.
- def body_part(headers : HTTP::Headers, body_io : IO)
+ def body_part(headers : HTTP::Headers, body_io : IO) : Nil
body_part_impl(headers) { |io| IO.copy(body_io, io) }
end
@@ -102,7 +102,7 @@ module MIME::Multipart
# Appends a body part to the multipart message with the given *headers*
# and no body data. Throws is `#finish` or `#epilogue` is called before
# this method.
- def body_part(headers : HTTP::Headers)
+ def body_part(headers : HTTP::Headers) : Nil
body_part_impl(headers, empty: true) { }
end
@@ -131,7 +131,7 @@ module MIME::Multipart
# appended.
#
# Can be called multiple times to append to the epilogue multiple times.
- def epilogue(string : String)
+ def epilogue(string : String) : Nil
epilogue { |io| string.to_s(io) }
end
@@ -140,7 +140,7 @@ module MIME::Multipart
# appended.
#
# Can be called multiple times to append to the epilogue multiple times.
- def epilogue(data : Bytes)
+ def epilogue(data : Bytes) : Nil
epilogue { |io| io.write data }
end
@@ -149,7 +149,7 @@ module MIME::Multipart
# been appended.
#
# Can be called multiple times to append to the epilogue multiple times.
- def epilogue(epilogue_io : IO)
+ def epilogue(epilogue_io : IO) : Nil
epilogue { |io| IO.copy(epilogue_io, io) }
end
@@ -174,7 +174,7 @@ module MIME::Multipart
# Finalizes the multipart message, this method must be called to properly
# end the multipart message.
- def finish
+ def finish : Nil
fail "Cannot finish multipart: no body parts" if @state == :START || @state == :PREAMBLE
fail "Cannot finish multipart: already finished" if @state == :FINISHED
diff --git a/src/named_tuple.cr b/src/named_tuple.cr
index 32d723e2ffd3..22a9a55e92a2 100644
--- a/src/named_tuple.cr
+++ b/src/named_tuple.cr
@@ -498,12 +498,18 @@ struct NamedTuple
# tuple = {name: "Crystal", year: 2011}
# tuple.to_a # => [{:name, "Crystal"}, {:year, 2011}]
# ```
+ #
+ # NOTE: `to_a` on an empty named tuple produces an `Array(Tuple(Symbol, NoReturn))`
def to_a
- ary = Array({typeof(first_key_internal), typeof(first_value_internal)}).new(size)
- each do |key, value|
- ary << {key.as(typeof(first_key_internal)), value.as(typeof(first_value_internal))}
- end
- ary
+ {% if T.size == 0 %}
+ [] of {Symbol, NoReturn}
+ {% else %}
+ [
+ {% for key in T %}
+ { {{key.symbolize}}, self[{{key.symbolize}}] },
+ {% end %}
+ ]
+ {% end %}
end
# Returns a `Hash` with the keys and values in this named tuple.
@@ -512,9 +518,11 @@ struct NamedTuple
# tuple = {name: "Crystal", year: 2011}
# tuple.to_h # => {:name => "Crystal", :year => 2011}
# ```
+ #
+ # NOTE: `to_h` on an empty named tuple produces a `Hash(Symbol, NoReturn)`
def to_h
{% if T.size == 0 %}
- {} of NoReturn => NoReturn
+ {} of Symbol => NoReturn
{% else %}
{
{% for key in T %}
diff --git a/src/oauth/params.cr b/src/oauth/params.cr
index 34a50fa22b2a..9689d4ac479c 100644
--- a/src/oauth/params.cr
+++ b/src/oauth/params.cr
@@ -4,13 +4,13 @@ struct OAuth::Params
@params = [] of {String, String}
end
- def add(key, value)
+ def add(key, value) : Nil
if value
@params << {URI.encode_www_form(key, space_to_plus: false), URI.encode_www_form(value, space_to_plus: false)}
end
end
- def add_query(query)
+ def add_query(query) : Nil
URI::Params.parse(query) do |key, value|
add key, value
end
diff --git a/src/oauth2/access_token/bearer.cr b/src/oauth2/access_token/bearer.cr
index 9e5a5c0148d3..c586dbb2cd47 100644
--- a/src/oauth2/access_token/bearer.cr
+++ b/src/oauth2/access_token/bearer.cr
@@ -13,7 +13,7 @@ class OAuth2::AccessToken::Bearer < OAuth2::AccessToken
request.headers["Authorization"] = "Bearer #{access_token}"
end
- def to_json(json : JSON::Builder)
+ def to_json(json : JSON::Builder) : Nil
json.object do
json.field "token_type", "bearer"
json.field "access_token", access_token
diff --git a/src/oauth2/access_token/mac.cr b/src/oauth2/access_token/mac.cr
index 670b57357662..6f7a8f44cbf4 100644
--- a/src/oauth2/access_token/mac.cr
+++ b/src/oauth2/access_token/mac.cr
@@ -45,7 +45,7 @@ class OAuth2::AccessToken::Mac < OAuth2::AccessToken
Base64.strict_encode OpenSSL::HMAC.digest(digest, mac_key, normalized_request_string)
end
- def to_json(json : JSON::Builder)
+ def to_json(json : JSON::Builder) : Nil
json.object do
json.field "token_type", "mac"
json.field "access_token", access_token
diff --git a/src/oauth2/session.cr b/src/oauth2/session.cr
index a3b56f8373c6..1ba9ec088cee 100644
--- a/src/oauth2/session.cr
+++ b/src/oauth2/session.cr
@@ -18,7 +18,7 @@ class OAuth2::Session
# Authenticates an `HTTP::Client`, refreshing the access token if it is expired.
#
# Invoke this method on an `HTTP::Client` before executing an HTTP request.
- def authenticate(http_client)
+ def authenticate(http_client) : Nil
check_refresh_token
@access_token.authenticate http_client
end
diff --git a/src/openssl/ssl/socket.cr b/src/openssl/ssl/socket.cr
index 22cf85d24dd1..fa74d45f466d 100644
--- a/src/openssl/ssl/socket.cr
+++ b/src/openssl/ssl/socket.cr
@@ -151,7 +151,7 @@ abstract class OpenSSL::SSL::Socket < IO
end
end
- def unbuffered_flush
+ def unbuffered_flush : Nil
@bio.io.flush
end
diff --git a/src/option_parser.cr b/src/option_parser.cr
index a7d6727f2a21..ec3f891759e3 100644
--- a/src/option_parser.cr
+++ b/src/option_parser.cr
@@ -220,7 +220,7 @@ class OptionParser
# before, and the flags registered after the call.
#
# This way, you can group the different options in an easier to read way.
- def separator(message = "")
+ def separator(message = "") : Nil
@flags << message.to_s
end
@@ -259,7 +259,7 @@ class OptionParser
# Stops the current parse and returns immediately, leaving the remaining flags
# unparsed. This is treated identically to `--` being inserted *behind* the
# current parsed flag.
- def stop
+ def stop : Nil
@stop = true
end
diff --git a/src/prelude.cr b/src/prelude.cr
index f391f0386ac2..79b5f838b03b 100644
--- a/src/prelude.cr
+++ b/src/prelude.cr
@@ -28,6 +28,7 @@ require "number"
require "annotations"
require "array"
require "atomic"
+require "base64"
require "bool"
require "box"
require "char"
diff --git a/src/pretty_print.cr b/src/pretty_print.cr
index 7dbf4be6a7cf..038da9d3eb4a 100644
--- a/src/pretty_print.cr
+++ b/src/pretty_print.cr
@@ -63,7 +63,7 @@ class PrettyPrint
end
# Appends a text element.
- def text(obj)
+ def text(obj) : Nil
obj = obj.to_s
width = obj.size
return if width == 0
@@ -84,7 +84,7 @@ class PrettyPrint
end
# Appends an element that can turn into a newline if necessary.
- def breakable(sep = " ")
+ def breakable(sep = " ") : Nil
width = sep.size
group = @group_stack.last
if group.break?
@@ -102,7 +102,7 @@ class PrettyPrint
# Similar to `#breakable` except
# the decision to break or not is determined individually.
- def fill_breakable(sep = " ")
+ def fill_breakable(sep = " ") : Nil
group { breakable sep }
end
@@ -149,7 +149,7 @@ class PrettyPrint
# text ","
# breakable
# ```
- def comma
+ def comma : Nil
text ","
breakable
end
@@ -185,7 +185,7 @@ class PrettyPrint
end
# Outputs any buffered data.
- def flush
+ def flush : Nil
@buffer.each do |data|
@output_width = data.output(@output, @output_width)
end
@@ -249,7 +249,7 @@ class PrettyPrint
@break = false
end
- def break
+ def break : Nil
@break = true
end
end
diff --git a/src/slice.cr b/src/slice.cr
index e33c1e40b7ae..f4e518dbf77b 100644
--- a/src/slice.cr
+++ b/src/slice.cr
@@ -351,6 +351,53 @@ struct Slice(T)
Slice.new(size, read_only: read_only) { |i| yield @pointer[i], offset + i }
end
+ # Replaces every element in `self` with the given *value*. Returns `self`.
+ #
+ # ```
+ # slice = Slice[1, 2, 3, 4]
+ # slice.fill(2) # => Slice[2, 2, 2, 2]
+ # slice # => Slice[2, 2, 2, 2]
+ # ```
+ def fill(value : T) : self
+ check_writable
+
+ {% if T == UInt8 %}
+ Intrinsics.memset(to_unsafe.as(Void*), value, size, false)
+ self
+ {% else %}
+ {% if Number::Primitive.union_types.includes?(T) %}
+ if value == 0
+ to_unsafe.clear(size)
+ return self
+ end
+ {% end %}
+
+ fill { value }
+ {% end %}
+ end
+
+ # Yields each index of `self` to the given block and then assigns
+ # the block's value in that position. Returns `self`.
+ #
+ # Accepts an optional *offset* parameter, which tells the block to start
+ # counting from there.
+ #
+ # ```
+ # slice = Slice[2, 1, 1, 1]
+ # slice.fill { |i| i * i } # => Slice[0, 1, 4, 9]
+ # slice # => Slice[0, 1, 4, 9]
+ # slice.fill(offset: 3) { |i| i * i } # => Slice[9, 16, 25, 36]
+ # slice # => Slice[9, 16, 25, 36]
+ # ```
+ def fill(*, offset : Int = 0, & : Int32 -> T) : self
+ check_writable
+
+ size.times do |i|
+ to_unsafe[i] = yield offset + i
+ end
+ self
+ end
+
def copy_from(source : Pointer(T), count)
check_writable
check_size(count)
@@ -690,8 +737,16 @@ struct Slice(T)
# a.sort # => Slice[1, 2, 3]
# a # => Slice[3, 1, 2]
# ```
- def sort(*, stable : Bool = true) : Slice(T)
- dup.sort!(stable: stable)
+ def sort : Slice(T)
+ dup.sort!
+ end
+
+ # :ditto:
+ #
+ # This method does not guarantee stability between equally sorting elements.
+ # Which results in a performance advantage over stable sort.
+ def unstable_sort : Slice(T)
+ dup.unstable_sort!
end
# Returns a new slice with all elements sorted based on the comparator in the
@@ -708,12 +763,24 @@ struct Slice(T)
# b # => Slice[3, 2, 1]
# a # => Slice[3, 1, 2]
# ```
- def sort(*, stable : Bool = true, &block : T, T -> U) : Slice(T) forall U
+ def sort(&block : T, T -> U) : Slice(T) forall U
{% unless U <= Int32? %}
{% raise "expected block to return Int32 or Nil, not #{U}" %}
{% end %}
- dup.sort!(stable: stable, &block)
+ dup.sort! &block
+ end
+
+ # :ditto:
+ #
+ # This method does not guarantee stability between equally sorting elements.
+ # Which results in a performance advantage over stable sort.
+ def unstable_sort(&block : T, T -> U) : Slice(T) forall U
+ {% unless U <= Int32? %}
+ {% raise "expected block to return Int32 or Nil, not #{U}" %}
+ {% end %}
+
+ dup.unstable_sort!(&block)
end
# Modifies `self` by sorting all elements based on the return value of their
@@ -724,12 +791,19 @@ struct Slice(T)
# a.sort!
# a # => Slice[1, 2, 3]
# ```
- def sort!(*, stable : Bool = true) : Slice(T)
- if stable
- Slice.merge_sort!(self)
- else
- Slice.intro_sort!(to_unsafe, size)
- end
+ def sort! : Slice(T)
+ Slice.merge_sort!(self)
+
+ self
+ end
+
+ # :ditto:
+ #
+ # This method does not guarantee stability between equally sorting elements.
+ # Which results in a performance advantage over stable sort.
+ def unstable_sort! : Slice(T)
+ Slice.intro_sort!(to_unsafe, size)
+
self
end
@@ -746,16 +820,27 @@ struct Slice(T)
# a.sort! { |a, b| b <=> a }
# a # => Slice[3, 2, 1]
# ```
- def sort!(*, stable : Bool = true, &block : T, T -> U) : Slice(T) forall U
+ def sort!(&block : T, T -> U) : Slice(T) forall U
{% unless U <= Int32? %}
{% raise "expected block to return Int32 or Nil, not #{U}" %}
{% end %}
- if stable
- Slice.merge_sort!(self, block)
- else
- Slice.intro_sort!(to_unsafe, size, block)
- end
+ Slice.merge_sort!(self, block)
+
+ self
+ end
+
+ # :ditto:
+ #
+ # This method does not guarantee stability between equally sorting elements.
+ # Which results in a performance advantage over stable sort.
+ def unstable_sort!(&block : T, T -> U) : Slice(T) forall U
+ {% unless U <= Int32? %}
+ {% raise "expected block to return Int32 or Nil, not #{U}" %}
+ {% end %}
+
+ Slice.intro_sort!(to_unsafe, size, block)
+
self
end
@@ -769,8 +854,16 @@ struct Slice(T)
# b # => Slice["fig", "pear", "apple"]
# a # => Slice["apple", "pear", "fig"]
# ```
- def sort_by(*, stable : Bool = true, &block : T -> _) : Slice(T)
- dup.sort_by!(stable: stable) { |e| yield(e) }
+ def sort_by(&block : T -> _) : Slice(T)
+ dup.sort_by! { |e| yield(e) }
+ end
+
+ # :ditto:
+ #
+ # This method does not guarantee stability between equally sorting elements.
+ # Which results in a performance advantage over stable sort.
+ def unstable_sort_by(&block : T -> _) : Slice(T)
+ dup.unstable_sort_by! { |e| yield(e) }
end
# Modifies `self` by sorting all elements. The given block is called for
@@ -782,8 +875,20 @@ struct Slice(T)
# a.sort_by! { |word| word.size }
# a # => Slice["fig", "pear", "apple"]
# ```
- def sort_by!(*, stable : Bool = true, &block : T -> _) : Slice(T)
- sorted = map { |e| {e, yield(e)} }.sort!(stable: stable) { |x, y| x[1] <=> y[1] }
+ def sort_by!(&block : T -> _) : Slice(T)
+ sorted = map { |e| {e, yield(e)} }.sort! { |x, y| x[1] <=> y[1] }
+ size.times do |i|
+ to_unsafe[i] = sorted.to_unsafe[i][0]
+ end
+ self
+ end
+
+ # :ditto:
+ #
+ # This method does not guarantee stability between equally sorting elements.
+ # Which results in a performance advantage over stable sort.
+ def unstable_sort_by!(&block : T -> _) : Slice(T)
+ sorted = map { |e| {e, yield(e)} }.unstable_sort! { |x, y| x[1] <=> y[1] }
size.times do |i|
to_unsafe[i] = sorted.to_unsafe[i][0]
end
diff --git a/src/static_array.cr b/src/static_array.cr
index cec9f6d7f2c4..85b74cde53d7 100644
--- a/src/static_array.cr
+++ b/src/static_array.cr
@@ -168,14 +168,8 @@ struct StaticArray(T, N)
# array # => StaticArray[2, 2, 2]
# ```
def fill(value : T) : self
- {% if Number::Primitive.union_types.includes?(T) %}
- if value == 0
- to_unsafe.clear(size)
- return self
- end
- {% end %}
-
- fill { value }
+ to_slice.fill(value)
+ self
end
# Yields each index of `self` to the given block and then assigns
@@ -187,9 +181,7 @@ struct StaticArray(T, N)
# array # => StaticArray[0, 1, 4, 9]
# ```
def fill(& : Int32 -> T) : self
- size.times do |i|
- to_unsafe[i] = yield i
- end
+ to_slice.fill { |i| yield i }
self
end
diff --git a/src/string.cr b/src/string.cr
index caefdae1243b..c2f0749da5e3 100644
--- a/src/string.cr
+++ b/src/string.cr
@@ -4958,7 +4958,7 @@ class String
# Appends `self` to *io*.
def to_s(io : IO) : Nil
- io.write_utf8(to_slice)
+ io.write_string(to_slice)
end
# Returns the underlying bytes of this String.
diff --git a/src/string/builder.cr b/src/string/builder.cr
index 6719a2c3e149..1ccd8de10897 100644
--- a/src/string/builder.cr
+++ b/src/string/builder.cr
@@ -64,6 +64,16 @@ class String::Builder < IO
nil
end
+ def write_string(slice : Bytes) : Nil
+ write(slice)
+ end
+
+ def set_encoding(encoding : String, invalid : Symbol? = nil) : Nil
+ unless utf8_encoding?(encoding, invalid)
+ raise "Can't change encoding of String::Builder"
+ end
+ end
+
def buffer : Pointer(UInt8)
@buffer + String::HEADER_SIZE
end
diff --git a/src/string/formatter.cr b/src/string/formatter.cr
index 55b32c1b22e4..44d14d67ac2b 100644
--- a/src/string/formatter.cr
+++ b/src/string/formatter.cr
@@ -273,7 +273,7 @@ struct String::Formatter(A)
temp_buf = temp_buf(len)
LibC.snprintf(temp_buf, len, format_buf, float)
- @io.write_utf8 Slice.new(temp_buf, len - 1)
+ @io.write_string Slice.new(temp_buf, len - 1)
else
raise ArgumentError.new("Expected a float, not #{arg.inspect}")
end
@@ -323,7 +323,7 @@ struct String::Formatter(A)
pad size, flags
end
- def char(char)
+ def char(char) : Nil
@io << char
end
diff --git a/src/time.cr b/src/time.cr
index fd090773bebd..d85fe3f3ab03 100644
--- a/src/time.cr
+++ b/src/time.cr
@@ -1113,7 +1113,7 @@ struct Time
#
# Number of seconds decimals can be selected with *fraction_digits*.
# Values accepted are 0 (the default, no decimals), 3 (milliseconds), 6 (microseconds) or 9 (nanoseconds).
- def to_rfc3339(io : IO, *, fraction_digits : Int = 0)
+ def to_rfc3339(io : IO, *, fraction_digits : Int = 0) : Nil
Format::RFC_3339.format(to_utc, io, fraction_digits)
end
diff --git a/src/time/format/custom/iso_8601.cr b/src/time/format/custom/iso_8601.cr
index 6d1331e2ee08..3cdaa4dd33e7 100644
--- a/src/time/format/custom/iso_8601.cr
+++ b/src/time/format/custom/iso_8601.cr
@@ -1,19 +1,19 @@
struct Time::Format
module Pattern
- def date_time_iso_8601
+ def date_time_iso_8601 : Nil
year_month_day_iso_8601
char? 'T'
time_iso_8601
end
- def time_iso_8601
+ def time_iso_8601 : Nil
hour_minute_second_iso8601
time_zone_z_or_offset
end
end
struct Parser
- def year_month_day_iso_8601
+ def year_month_day_iso_8601 : Nil
year
extended_format = char? '-'
if current_char == 'W'
@@ -58,7 +58,7 @@ struct Time::Format
end
end
- def hour_minute_second_iso8601
+ def hour_minute_second_iso8601 : Nil
hour_24_zero_padded
decimal_seconds = Time::SECONDS_PER_HOUR
@@ -106,7 +106,7 @@ struct Time::Format
end
struct Formatter
- def year_month_day_iso_8601
+ def year_month_day_iso_8601 : Nil
year_month_day
end
diff --git a/src/time/format/formatter.cr b/src/time/format/formatter.cr
index 1abe28655104..3eafed806857 100644
--- a/src/time/format/formatter.cr
+++ b/src/time/format/formatter.cr
@@ -11,143 +11,143 @@ struct Time::Format
def initialize(@time : Time, @io : IO)
end
- def year
+ def year : Nil
pad4(time.year, '0')
end
- def year_modulo_100
+ def year_modulo_100 : Nil
pad2(time.year % 100, '0')
end
- def year_divided_by_100
+ def year_divided_by_100 : Nil
io << time.year // 100
end
- def full_or_short_year
+ def full_or_short_year : Nil
year
end
- def calendar_week_year
+ def calendar_week_year : Nil
pad4(time.calendar_week[0], '0')
end
- def calendar_week_year_modulo100
+ def calendar_week_year_modulo100 : Nil
pad2(time.calendar_week[0] % 100, '0')
end
- def month
+ def month : Nil
io << time.month
end
- def month_zero_padded
+ def month_zero_padded : Nil
pad2 time.month, '0'
end
- def month_blank_padded
+ def month_blank_padded : Nil
pad2 time.month, ' '
end
- def month_name
+ def month_name : Nil
io << get_month_name
end
- def month_name_upcase
+ def month_name_upcase : Nil
io << get_month_name.upcase
end
- def short_month_name
+ def short_month_name : Nil
io << get_short_month_name
end
- def short_month_name_upcase
+ def short_month_name_upcase : Nil
io << get_short_month_name.upcase
end
- def calendar_week_week
+ def calendar_week_week : Nil
pad2(time.calendar_week[1], '0')
end
- def day_of_month
+ def day_of_month : Nil
io << time.day
end
- def day_of_month_zero_padded
+ def day_of_month_zero_padded : Nil
pad2 time.day, '0'
end
- def day_of_month_blank_padded
+ def day_of_month_blank_padded : Nil
pad2 time.day, ' '
end
- def day_name
+ def day_name : Nil
io << get_day_name
end
- def day_name_upcase
+ def day_name_upcase : Nil
io << get_day_name.upcase
end
- def short_day_name
+ def short_day_name : Nil
io << get_short_day_name
end
- def short_day_name_upcase
+ def short_day_name_upcase : Nil
io << get_short_day_name.upcase
end
- def short_day_name_with_comma?
+ def short_day_name_with_comma? : Nil
short_day_name
char ','
whitespace
end
- def day_of_year_zero_padded
+ def day_of_year_zero_padded : Nil
pad3 time.day_of_year, '0'
end
- def hour_24_zero_padded
+ def hour_24_zero_padded : Nil
pad2 time.hour, '0'
end
- def hour_24_blank_padded
+ def hour_24_blank_padded : Nil
pad2 time.hour, ' '
end
- def hour_12_zero_padded
+ def hour_12_zero_padded : Nil
h = (time.hour % 12)
pad2 (h == 0 ? 12 : h), '0'
end
- def hour_12_blank_padded
+ def hour_12_blank_padded : Nil
h = (time.hour % 12)
pad2 (h == 0 ? 12 : h), ' '
end
- def minute
+ def minute : Nil
pad2 time.minute, '0'
end
- def second
+ def second : Nil
pad2 time.second, '0'
end
- def milliseconds
+ def milliseconds : Nil
pad3 time.millisecond, '0'
end
- def microseconds
+ def microseconds : Nil
pad6 time.nanosecond // 1000, '0'
end
- def nanoseconds
+ def nanoseconds : Nil
pad9 time.nanosecond, '0'
end
- def second_fraction
+ def second_fraction : Nil
nanoseconds
end
- def second_fraction?(fraction_digits : Int = 9)
+ def second_fraction?(fraction_digits : Int = 9) : Nil
case fraction_digits
when 0
when 3 then char '.'; milliseconds
@@ -158,31 +158,31 @@ struct Time::Format
end
end
- def am_pm
+ def am_pm : Nil
io << (time.hour < 12 ? "am" : "pm")
end
- def am_pm_upcase
+ def am_pm_upcase : Nil
io << (time.hour < 12 ? "AM" : "PM")
end
- def day_of_week_monday_1_7
+ def day_of_week_monday_1_7 : Nil
io << time.day_of_week.value
end
- def day_of_week_sunday_0_6
+ def day_of_week_sunday_0_6 : Nil
io << time.day_of_week.value % 7
end
- def unix_seconds
+ def unix_seconds : Nil
io << time.to_unix
end
- def time_zone(with_seconds = false)
+ def time_zone(with_seconds = false) : Nil
time_zone_offset(format_seconds: with_seconds)
end
- def time_zone_z_or_offset(**options)
+ def time_zone_z_or_offset(**options) : Nil
if time.utc?
io << 'Z'
else
@@ -194,23 +194,23 @@ struct Time::Format
time.zone.format(io, with_colon: force_colon, with_seconds: format_seconds)
end
- def time_zone_colon(with_seconds = false)
+ def time_zone_colon(with_seconds = false) : Nil
time_zone_offset(force_colon: true, format_seconds: with_seconds)
end
- def time_zone_colon_with_seconds
+ def time_zone_colon_with_seconds : Nil
time_zone_colon(with_seconds: true)
end
- def time_zone_gmt
+ def time_zone_gmt : Nil
io << "GMT"
end
- def time_zone_rfc2822
+ def time_zone_rfc2822 : Nil
time_zone_offset(allow_colon: false)
end
- def time_zone_gmt_or_rfc2822(**options)
+ def time_zone_gmt_or_rfc2822(**options) : Nil
if time.utc? || time.location.name == "UT" || time.location.name == "GMT"
time_zone_gmt
else
@@ -218,7 +218,7 @@ struct Time::Format
end
end
- def time_zone_name(zone = false)
+ def time_zone_name(zone = false) : Nil
if zone
io << time.zone.name
else
@@ -226,7 +226,7 @@ struct Time::Format
end
end
- def char(char, *alternatives)
+ def char(char, *alternatives) : Nil
io << char
end
@@ -234,7 +234,7 @@ struct Time::Format
char(char, *alternatives)
end
- def whitespace
+ def whitespace : Nil
io << ' '
end
@@ -254,28 +254,28 @@ struct Time::Format
get_day_name[0, 3]
end
- def pad2(value, padding)
+ def pad2(value, padding) : Nil
io.write_byte padding.ord.to_u8 if value < 10
io << value
end
- def pad3(value, padding)
+ def pad3(value, padding) : Nil
io.write_byte padding.ord.to_u8 if value < 100
pad2 value, padding
end
- def pad4(value, padding)
+ def pad4(value, padding) : Nil
io.write_byte padding.ord.to_u8 if value < 1000
pad3 value, padding
end
- def pad6(value, padding)
+ def pad6(value, padding) : Nil
io.write_byte padding.ord.to_u8 if value < 100000
io.write_byte padding.ord.to_u8 if value < 10000
pad4 value, padding
end
- def pad9(value, padding)
+ def pad9(value, padding) : Nil
io.write_byte padding.ord.to_u8 if value < 100000000
io.write_byte padding.ord.to_u8 if value < 10000000
io.write_byte padding.ord.to_u8 if value < 1000000
diff --git a/src/tuple.cr b/src/tuple.cr
index adff9a4d3827..ddf4e92e00e5 100644
--- a/src/tuple.cr
+++ b/src/tuple.cr
@@ -530,6 +530,34 @@ struct Tuple
nil
end
+ # :inherit:
+ def reduce
+ {% if T.empty? %}
+ raise Enumerable::EmptyError.new
+ {% else %}
+ memo = self[0]
+ {% for i in 1...T.size %}
+ memo = yield memo, self[{{ i }}]
+ {% end %}
+ memo
+ {% end %}
+ end
+
+ # :inherit:
+ def reduce(memo)
+ {% for i in 0...T.size %}
+ memo = yield memo, self[{{ i }}]
+ {% end %}
+ memo
+ end
+
+ # :inherit:
+ def reduce?
+ {% unless T.empty? %}
+ reduce { |memo, elem| yield memo, elem }
+ {% end %}
+ end
+
# Returns the first element of this tuple. Doesn't compile
# if the tuple is empty.
#
diff --git a/src/uuid/json.cr b/src/uuid/json.cr
index d9b56f7258ca..040668f4a6b8 100644
--- a/src/uuid/json.cr
+++ b/src/uuid/json.cr
@@ -32,7 +32,7 @@ struct UUID
# uuid = UUID.new("87b3042b-9b9a-41b7-8b15-a93d3f17025e")
# uuid.to_json # => "\"87b3042b-9b9a-41b7-8b15-a93d3f17025e\""
# ```
- def to_json(json : JSON::Builder)
+ def to_json(json : JSON::Builder) : Nil
json.string(to_s)
end
diff --git a/src/xml/builder.cr b/src/xml/builder.cr
index 742054bbccdf..e50bba52ca66 100644
--- a/src/xml/builder.cr
+++ b/src/xml/builder.cr
@@ -247,7 +247,7 @@ class XML::Builder
# Forces content written to this writer to be flushed to
# this writer's `IO`.
- def flush
+ def flush : Nil
call Flush
@io.flush
diff --git a/src/xml/node.cr b/src/xml/node.cr
index cac2abac2bdb..c3fa4d0dd26a 100644
--- a/src/xml/node.cr
+++ b/src/xml/node.cr
@@ -294,6 +294,19 @@ class XML::Node
end
end
+ # Returns namespaces defined on self element directly.
+ def namespace_definitions : Array(Namespace)
+ namespaces = [] of Namespace
+
+ ns = @node.value.ns_def
+ while ns
+ namespaces << Namespace.new(document, ns)
+ ns = ns.value.next
+ end
+
+ namespaces
+ end
+
# Returns namespaces in scope for self – those defined on self element
# directly or any ancestor node – as an `Array` of `XML::Namespace` objects.
#
@@ -304,13 +317,8 @@ class XML::Node
def namespace_scopes : Array(Namespace)
scopes = [] of Namespace
- ns_list = LibXML.xmlGetNsList(@node.value.doc, @node)
-
- if ns_list
- while ns_list.value
- scopes << Namespace.new(document, ns_list.value)
- ns_list += 1
- end
+ each_namespace do |namespace|
+ scopes << namespace
end
scopes
diff --git a/src/yaml/any.cr b/src/yaml/any.cr
index 880f0c75c330..71b6b2e2e379 100644
--- a/src/yaml/any.cr
+++ b/src/yaml/any.cr
@@ -312,7 +312,7 @@ struct YAML::Any
raw.to_yaml(io)
end
- def to_json(builder : JSON::Builder)
+ def to_json(builder : JSON::Builder) : Nil
if (raw = self.raw).is_a?(Slice)
raise "Can't serialize #{raw.class} to JSON"
else
diff --git a/src/yaml/builder.cr b/src/yaml/builder.cr
index 1e96dd5142a8..73ec669c25cf 100644
--- a/src/yaml/builder.cr
+++ b/src/yaml/builder.cr
@@ -61,7 +61,7 @@ class YAML::Builder
end
# Ends a YAML stream.
- def end_stream
+ def end_stream : Nil
emit stream_end
flush
end
@@ -96,14 +96,14 @@ class YAML::Builder
end
# Starts a sequence.
- def start_sequence(anchor : String? = nil, tag : String? = nil, style : YAML::SequenceStyle = YAML::SequenceStyle::ANY)
+ def start_sequence(anchor : String? = nil, tag : String? = nil, style : YAML::SequenceStyle = YAML::SequenceStyle::ANY) : Nil
implicit = tag ? 0 : 1
emit sequence_start, get_anchor(anchor), string_to_unsafe(tag), implicit, style
increase_nesting
end
# Ends a sequence.
- def end_sequence
+ def end_sequence : Nil
emit sequence_end
decrease_nesting
end
@@ -115,14 +115,14 @@ class YAML::Builder
end
# Starts a mapping.
- def start_mapping(anchor : String? = nil, tag : String? = nil, style : YAML::MappingStyle = YAML::MappingStyle::ANY)
+ def start_mapping(anchor : String? = nil, tag : String? = nil, style : YAML::MappingStyle = YAML::MappingStyle::ANY) : Nil
implicit = tag ? 0 : 1
emit mapping_start, get_anchor(anchor), string_to_unsafe(tag), implicit, style
increase_nesting
end
# Ends a mapping.
- def end_mapping
+ def end_mapping : Nil
emit mapping_end
decrease_nesting
end
@@ -185,7 +185,7 @@ class YAML::Builder
end
# Closes the builder, freeing up resources.
- def close
+ def close : Nil
finalize
@closed = true
end
diff --git a/src/yaml/nodes/parser.cr b/src/yaml/nodes/parser.cr
index 20fd0aed550b..d63cf093196c 100644
--- a/src/yaml/nodes/parser.cr
+++ b/src/yaml/nodes/parser.cr
@@ -46,7 +46,7 @@ class YAML::Nodes::Parser < YAML::Parser
node.start_column = @pull_parser.start_column.to_i
end
- def end_value(node)
+ def end_value(node) : Nil
node.end_line = @pull_parser.end_line.to_i
node.end_column = @pull_parser.end_column.to_i
end
@@ -72,15 +72,15 @@ class YAML::Nodes::Parser < YAML::Parser
documents << document
end
- def add_to_document(document, node)
+ def add_to_document(document, node) : Nil
document << node
end
- def add_to_sequence(sequence, node)
+ def add_to_sequence(sequence, node) : Nil
sequence << node
end
- def add_to_mapping(mapping, key, value)
+ def add_to_mapping(mapping, key, value) : Nil
mapping[key] = value
end
end
diff --git a/src/yaml/parser.cr b/src/yaml/parser.cr
index 547ff56a72e8..5b27cee2a3b9 100644
--- a/src/yaml/parser.cr
+++ b/src/yaml/parser.cr
@@ -157,7 +157,7 @@ abstract class YAML::Parser
end
# Closes this parser, freeing up resources.
- def close
+ def close : Nil
@pull_parser.close
end
diff --git a/src/yaml/pull_parser.cr b/src/yaml/pull_parser.cr
index f24a8a5182a8..1cdfa8131d92 100644
--- a/src/yaml/pull_parser.cr
+++ b/src/yaml/pull_parser.cr
@@ -260,24 +260,24 @@ class YAML::PullParser
# Note: YAML starts counting from 0, we want to count from 1
- def location
+ def location : {Int32, Int32}
{start_line, start_column}
end
- def start_line : Int
- @event.start_mark.line + 1
+ def start_line : Int32
+ @event.start_mark.line.to_i32 + 1
end
- def start_column : Int
- @event.start_mark.column + 1
+ def start_column : Int32
+ @event.start_mark.column.to_i32 + 1
end
- def end_line : Int
- @event.end_mark.line + 1
+ def end_line : Int32
+ @event.end_mark.line.to_i32 + 1
end
- def end_column : Int
- @event.end_mark.column + 1
+ def end_column : Int32
+ @event.end_mark.column.to_i32 + 1
end
private def problem_line_number
@@ -321,7 +321,7 @@ class YAML::PullParser
LibYAML.yaml_event_delete(pointerof(@event))
end
- def close
+ def close : Nil
finalize
@closed = true
end
diff --git a/src/yaml/schema/core/parser.cr b/src/yaml/schema/core/parser.cr
index 0b4402b853a7..bc17ef63856d 100644
--- a/src/yaml/schema/core/parser.cr
+++ b/src/yaml/schema/core/parser.cr
@@ -36,15 +36,15 @@ class YAML::Schema::Core::Parser < YAML::Parser
Any.new(Core.parse_scalar(@pull_parser))
end
- def add_to_documents(documents, document)
+ def add_to_documents(documents, document) : Nil
documents << document
end
- def add_to_document(document, node)
+ def add_to_document(document, node) : Nil
document.as_a << node
end
- def add_to_sequence(sequence, node)
+ def add_to_sequence(sequence, node) : Nil
sequence.as_a << node
end
diff --git a/src/yaml/schema/fail_safe.cr b/src/yaml/schema/fail_safe.cr
index f309537f67b8..2518f104c447 100644
--- a/src/yaml/schema/fail_safe.cr
+++ b/src/yaml/schema/fail_safe.cr
@@ -50,19 +50,19 @@ module YAML::Schema::FailSafe
Any.new(@pull_parser.value)
end
- def add_to_documents(documents, document)
+ def add_to_documents(documents, document) : Nil
documents << document
end
- def add_to_document(document, node)
+ def add_to_document(document, node) : Nil
document.as_a << node
end
- def add_to_sequence(sequence, node)
+ def add_to_sequence(sequence, node) : Nil
sequence.as_a << node
end
- def add_to_mapping(mapping, key, value)
+ def add_to_mapping(mapping, key, value) : Nil
mapping.as_h[key] = value
end
end