From 1df81a4ba49c81a5e4c24a3719201fc81b501b5c Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Wed, 16 Feb 2022 20:27:24 +0800 Subject: [PATCH 1/5] Support tuple metaclass indexers with non-literal indices --- spec/std/named_tuple_spec.cr | 186 +++++++++++++++++++++++++---------- spec/std/tuple_spec.cr | 103 +++++++++++++------ src/named_tuple.cr | 90 ++++++++++++++++- src/tuple.cr | 112 +++++++++++++++++++-- 4 files changed, 393 insertions(+), 98 deletions(-) diff --git a/spec/std/named_tuple_spec.cr b/spec/std/named_tuple_spec.cr index 86aa97cc6f4b..7f94ba133a0e 100644 --- a/spec/std/named_tuple_spec.cr +++ b/spec/std/named_tuple_spec.cr @@ -80,80 +80,160 @@ describe "NamedTuple" do {a: 1, b: 3}.size.should eq(2) end - it "does [] with runtime key" do - tup = {a: 1, b: 'a'} + describe "#[] with non-literal index" do + it "gets named tuple value with Symbol key" do + tup = {a: 1, b: 'a'} + + key = :a + val = tup[key] + val.should eq(1) + typeof(val).should eq(Int32 | Char) + + key = :b + val = tup[key] + val.should eq('a') + typeof(val).should eq(Int32 | Char) + end - key = :a - val = tup[key] - val.should eq(1) - typeof(val).should eq(Int32 | Char) + it "gets named tuple value with String key" do + tup = {a: 1, b: 'a'} + + key = "a" + val = tup[key] + val.should eq(1) + typeof(val).should eq(Int32 | Char) - key = :b - val = tup[key] - val.should eq('a') - typeof(val).should eq(Int32 | Char) + key = "b" + val = tup[key] + val.should eq('a') + typeof(val).should eq(Int32 | Char) + end - expect_raises(KeyError) do + it "raises missing key" do + tup = {a: 1, b: 'a'} key = :c - tup[key] + expect_raises(KeyError) { tup[key] } + key = "d" + expect_raises(KeyError) { tup[key] } end end - it "does []? with runtime key" do - tup = {a: 1, b: 'a'} + describe "#[]? with non-literal index" do + it "gets named tuple value or nil with Symbol key" do + tup = {a: 1, b: 'a'} - key = :a - val = tup[key]? - val.should eq(1) - typeof(val).should eq(Int32 | Char | Nil) + key = :a + val = tup[key]? + val.should eq(1) + typeof(val).should eq(Int32 | Char | Nil) - key = :b - val = tup[key]? - val.should eq('a') - typeof(val).should eq(Int32 | Char | Nil) + key = :b + val = tup[key]? + val.should eq('a') + typeof(val).should eq(Int32 | Char | Nil) - key = :c - val = tup[key]? - val.should be_nil - typeof(val).should eq(Int32 | Char | Nil) - end + key = :c + val = tup[key]? + val.should be_nil + typeof(val).should eq(Int32 | Char | Nil) + end - it "does [] with string" do - tup = {a: 1, b: 'a'} + it "gets named tuple value or nil with String key" do + tup = {a: 1, b: 'a'} - key = "a" - val = tup[key] - val.should eq(1) - typeof(val).should eq(Int32 | Char) + key = "a" + val = tup[key]? + val.should eq(1) + typeof(val).should eq(Int32 | Char | Nil) - key = "b" - val = tup[key] - val.should eq('a') - typeof(val).should eq(Int32 | Char) + key = "b" + val = tup[key]? + val.should eq('a') + typeof(val).should eq(Int32 | Char | Nil) - expect_raises(KeyError) do key = "c" - tup[key] + val = tup[key]? + val.should be_nil + typeof(val).should eq(Int32 | Char | Nil) end end - it "does []? with string" do - tup = {a: 1, b: 'a'} + describe ".[] with non-literal index" do + it "gets named tuple metaclass value with Symbol key" do + tup = NamedTuple(a: Int32, b: Char) - key = "a" - val = tup[key]? - val.should eq(1) - typeof(val).should eq(Int32 | Char | Nil) + key = :a + val = tup[key] + val.should eq(Int32) + typeof(val).should eq(Union(Int32.class, Char.class)) + + key = :b + val = tup[key] + val.should eq(Char) + typeof(val).should eq(Union(Int32.class, Char.class)) + end - key = "b" - val = tup[key]? - val.should eq('a') - typeof(val).should eq(Int32 | Char | Nil) + it "gets named tuple metaclass value with String key" do + tup = NamedTuple(a: Int32, b: Char) - key = "c" - val = tup[key]? - val.should be_nil - typeof(val).should eq(Int32 | Char | Nil) + key = "a" + val = tup[key] + val.should eq(Int32) + typeof(val).should eq(Union(Int32.class, Char.class)) + + key = "b" + val = tup[key] + val.should eq(Char) + typeof(val).should eq(Union(Int32.class, Char.class)) + end + + it "raises missing key" do + tup = NamedTuple(a: Int32, b: Char) + key = :c + expect_raises(KeyError) { tup[key] } + key = "d" + expect_raises(KeyError) { tup[key] } + end + end + + describe ".[]? with non-literal index" do + it "gets named tuple metaclass value or nil with Symbol key" do + tup = NamedTuple(a: Int32, b: Char) + + key = :a + val = tup[key]? + val.should eq(Int32) + typeof(val).should eq(Union(Int32.class, Char.class, Nil)) + + key = :b + val = tup[key]? + val.should eq(Char) + typeof(val).should eq(Union(Int32.class, Char.class, Nil)) + + key = :c + val = tup[key]? + val.should be_nil + typeof(val).should eq(Union(Int32.class, Char.class, Nil)) + end + + it "gets named tuple metaclass value or nil with String key" do + tup = NamedTuple(a: Int32, b: Char) + + key = "a" + val = tup[key]? + val.should eq(Int32) + typeof(val).should eq(Union(Int32.class, Char.class, Nil)) + + key = "b" + val = tup[key]? + val.should eq(Char) + typeof(val).should eq(Union(Int32.class, Char.class, Nil)) + + key = "c" + val = tup[key]? + val.should be_nil + typeof(val).should eq(Union(Int32.class, Char.class, Nil)) + end end describe "#dig?" do diff --git a/spec/std/tuple_spec.cr b/spec/std/tuple_spec.cr index 4dd9a49b63b0..95b0d927e4fb 100644 --- a/spec/std/tuple_spec.cr +++ b/spec/std/tuple_spec.cr @@ -24,36 +24,79 @@ describe "Tuple" do {1}.empty?.should be_false end - it "does []" do - a = {1, 2.5} - i = 0 - a[i].should eq(1) - i = 1 - a[i].should eq(2.5) - i = -1 - a[i].should eq(2.5) - i = -2 - a[i].should eq(1) - end - - it "does [] raises index out of bounds" do - a = {1, 2.5} - i = 2 - expect_raises(IndexError) { a[i] } - i = -3 - expect_raises(IndexError) { a[i] } - end - - it "does []?" do - a = {1, 2} - i = 1 - a[i]?.should eq(2) - i = -1 - a[i]?.should eq(2) - i = 2 - a[i]?.should be_nil - i = -3 - a[i]?.should be_nil + describe "#[] with non-literal index" do + it "gets tuple element" do + a = {1, 2.5} + i = 0 + a[i].should eq(1) + i = 1 + a[i].should eq(2.5) + i = -1 + a[i].should eq(2.5) + i = -2 + a[i].should eq(1) + typeof(a[i]).should eq(Int32 | Float64) + end + + it "raises index out of bounds" do + a = {1, 2.5} + i = 2 + expect_raises(IndexError) { a[i] } + i = -3 + expect_raises(IndexError) { a[i] } + end + end + + describe "#[]? with non-literal index" do + it "gets tuple element or nil" do + a = {1, 2.5} + i = 0 + a[i]?.should eq(1) + i = -1 + a[i]?.should eq(2.5) + i = 2 + a[i]?.should be_nil + i = -3 + a[i]?.should be_nil + typeof(a[i]?).should eq(Int32 | Float64 | Nil) + end + end + + describe ".[] with non-literal index" do + it "gets tuple metaclass element" do + a = Tuple(Int32, Float64) + i = 0 + a[i].should eq(Int32) + i = 1 + a[i].should eq(Float64) + i = -1 + a[i].should eq(Float64) + i = -2 + a[i].should eq(Int32) + end + + it "raises index out of bounds" do + a = Tuple(Int32, Float64) + i = 2 + expect_raises(IndexError) { a[i] } + i = -3 + expect_raises(IndexError) { a[i] } + end + end + + describe ".[]? with non-literal index" do + it "gets tuple metaclass element or nil" do + a = Tuple(Int32, Float64) + i = 0 + a[i]?.should eq(Int32) + i = -1 + a[i]?.should eq(Float64) + i = 2 + a[i]?.should be_nil + i = -3 + a[i]?.should be_nil + typeof(a[i]?).should eq(Union(Int32.class, Float64.class, Nil)) + end end it "does at" do diff --git a/src/named_tuple.cr b/src/named_tuple.cr index 22a9a55e92a2..c9e6483a4ca3 100644 --- a/src/named_tuple.cr +++ b/src/named_tuple.cr @@ -106,13 +106,23 @@ struct NamedTuple {% end %} end - # Returns the value for the given *key*, if there's such key, otherwise raises `KeyError`. + # Returns the value for the given *key* if there is such a key, otherwise + # raises `KeyError`. + # Read the type docs to understand the difference between indexing with a + # literal or a variable. # # ``` # tuple = {name: "Crystal", year: 2011} # + # tuple[:name] # => "Crystal" + # typeof(tuple[:name]) # => String + # tuple["year"] # => 2011 + # typeof(tuple["year"]) # => Int32 + # tuple[:other] # Error: missing key 'other' for named tuple NamedTuple(name: String, year: Int32) + # # key = :name - # tuple[key] # => "Crystal" + # tuple[key] # => "Crystal" + # typeof(tuple[key]) # => (Int32 | String) # # key = "year" # tuple[key] # => 2011 @@ -124,24 +134,94 @@ struct NamedTuple fetch(key) { raise KeyError.new "Missing named tuple key: #{key.inspect}" } end - # Returns the value for the given *key*, if there's such key, otherwise returns `nil`. + # Returns the value type for the given *key* if there is such a key, otherwise + # raises `KeyError`. + # Read the type docs to understand the difference between indexing with a + # literal or a variable. + # + # ``` + # alias Foo = NamedTuple(name: String, year: Int32) + # + # Foo[:name] # => String + # Foo["year"] # => Int32 + # Foo["year"].zero # => 0 + # Foo[:other] # => Error: missing key 'other' for named tuple NamedTuple(name: String, year: Int32).class + # + # key = :year + # Foo[key] # => Int32 + # Foo[key].zero # Error: undefined method '[]' for NamedTuple(name: String, year: Int32).class + # + # key = "other" + # Foo[key] # raises KeyError + # ``` + def self.[](key : Symbol | String) + self[key]? || raise KeyError.new "Missing named tuple key: #{key.inspect}" + end + + # Returns the value for the given *key* if there is such a key, otherwise + # returns `nil`. + # Read the type docs to understand the difference between indexing with a + # literal or a variable. # # ``` # tuple = {name: "Crystal", year: 2011} # + # tuple[:name]? # => "Crystal" + # typeof(tuple[:name]?) # => String + # tuple["year"]? # => 2011 + # typeof(tuple["year"]?) # => Int32 + # tuple[:other]? # => nil + # typeof(tuple[:other]?) # => Nil + # # key = :name - # tuple[key]? # => "Crystal" + # tuple[key]? # => "Crystal" + # typeof(tuple[key]?) # => (Int32 | String | Nil) # # key = "year" # tuple[key] # => 2011 # # key = :other - # tuple[key]? # => nil + # tuple[key] # => nil # ``` def []?(key : Symbol | String) fetch(key, nil) end + # Returns the value type for the given *key* if there is such a key, otherwise + # returns `nil`. + # Read the type docs to understand the difference between indexing with a + # literal or a variable. + # + # ``` + # alias Foo = NamedTuple(name: String, year: Int32) + # + # Foo[:name]? # => String + # Foo["year"]? # => Int32 + # Foo["year"]?.zero # => 0 + # Foo[:other]? # => nil + # typeof(Foo[:other]?) # => Nil + # + # key = :year + # Foo[key]? # => Int32 + # Foo[key]?.zero # Error: undefined method 'zero' for String.class (compile-time type is (Int32.class | String.class | Nil)) + # + # key = "other" + # Foo[key]? # => nil + # ``` + def self.[]?(key : Symbol | String) + {% begin %} + case key + {% for key in T %} + when {{ key.symbolize }}, {{ key.stringify }} + typeof(begin + x = uninitialized self + x[{{ key.symbolize }}] + end) + {% end %} + end + {% end %} + end + # Traverses the depth of a structure and returns the value. # Returns `nil` if not found. # diff --git a/src/tuple.cr b/src/tuple.cr index 878c56451eb9..84b9bbd17d45 100644 --- a/src/tuple.cr +++ b/src/tuple.cr @@ -162,11 +162,13 @@ struct Tuple # # ``` # tuple = {1, "hello", 'x'} - # tuple[0] # => 1 (Int32) - # tuple[3] # compile error: index out of bounds for tuple {Int32, String, Char} + # tuple[0] # => 1 + # typeof(tuple[0]) # => Int32 + # tuple[3] # Error: index out of bounds for Tuple(Int32, String, Char) (3 not in -3..2) # # i = 0 - # tuple[i] # => 1 (Int32 | String | Char) + # tuple[i] # => 1 + # typeof(tuple[i]) # => (Char | Int32 | String) # # i = 3 # tuple[i] # raises IndexError @@ -175,23 +177,87 @@ struct Tuple at(index) end - # Returns the element at the given *index* or `nil` if out of bounds. + # Returns the element type at the given *index*. Read the type docs to + # understand the difference between indexing with a number literal or a + # variable. + # + # ``` + # alias Foo = Tuple(Int32, String) + # Foo[0] # => Int32 + # Foo[0].zero # => 0 + # Foo[2] # Error: index out of bounds for Tuple(Int32, String).class (2 not in -2..1) + # + # i = 0 + # Foo[i] # => Int32 + # Foo[i].zero # Error: undefined method 'zero' for String.class (compile-time type is (Int32.class | String.class)) + # + # i = 2 + # Foo[i] # raises IndexError + # ``` + def self.[](index : Int) + self[index]? || raise IndexError.new + end + + # Returns the element at the given *index* or `nil` if out of bounds. Read the + # type docs to understand the difference between indexing with a number + # literal or a variable. # # ``` # tuple = {1, "hello", 'x'} - # tuple[0]? # => 1 - # tuple[3]? # => nil + # tuple[0]? # => 1 + # typeof(tuple[0]?) # => Int32 + # tuple[3]? # => nil + # typeof(tuple[3]?) # => Nil + # + # i = 0 + # tuple[i]? # => 1 + # typeof(tuple[i]?) # => (Char | Int32 | String | Nil) + # + # i = 3 + # tuple[i]? # => nil # ``` def []?(index : Int) at(index) { nil } end + # Returns the element type at the given *index* or `nil` if out of bounds. + # Read the type docs to understand the difference between indexing with a + # number literal or a variable. + # + # ``` + # alias Foo = Tuple(Int32, String) + # Foo[0]? # => Int32 + # Foo[0]?.zero # => 0 + # Foo[2]? # => nil + # typeof(Foo[2]?) # => Nil + # + # i = 0 + # Foo[i]? # => Int32 + # Foo[i]?.zero # Error: undefined method 'zero' for String.class (compile-time type is (Int32.class | String.class | Nil)) + # + # i = 2 + # Foo[i]? # => nil + # ``` + def self.[]?(index : Int) + {% begin %} + case index + {% for i in 0...T.size %} + when {{ i }}, {{ i - T.size }} + typeof(begin + v = uninitialized self + v[{{ i }}] + end) + {% end %} + end + {% end %} + end + # Returns all elements that are within the given *range*. *range* must be a # range literal whose value is known at compile-time. # - # Negative indices count backward from the end of the array (-1 is the last - # element). Additionally, an empty array is returned when the starting index - # for an element range is at the end of the array. + # Negative indices count backward from the end of the tuple (-1 is the last + # element). Additionally, an empty tuple is returned when the starting index + # for an element range is at the end of the tuple. # # Raises a compile-time error if `range.begin` is out of range. # @@ -200,7 +266,7 @@ struct Tuple # tuple[0..1] # => {1, "hello"} # tuple[-2..] # => {"hello", 'x'} # tuple[...1] # => {1} - # tuple[4..] # Error: begin index out of bounds for Tuple(Int32, Char, Array(Int32), String) (5 not in -4..4) + # tuple[4..] # Error: begin index out of bounds for Tuple(Int32, String, Char) (4 not in -3..3) # # i = 0 # tuple[i..2] # Error: Tuple#[](Range) can only be called with range literals known at compile-time @@ -212,6 +278,32 @@ struct Tuple {% raise "Tuple#[](Range) can only be called with range literals known at compile-time" %} end + # Returns all element types that are within the given *range*. *range* must be + # a range literal whose value is known at compile-time. + # + # Negative indices count backward from the end of the tuple (-1 is the last + # element). Additionally, an empty tuple is returned when the starting index + # for an element range is at the end of the tuple. + # + # Raises a compile-time error if `range.begin` is out of range. + # + # ``` + # alias Foo = Tuple(Int32, String, Char) + # Foo[0..1] # => Tuple(Int32, String) + # Foo[-2..] # => Tuple(String, Char) + # Foo[...1] # => Tuple(Int32) + # Foo[4..] # Error: begin index out of bounds for Tuple(Int32, String, Char).class (4 not in -3..3) + # + # i = 0 + # Foo[i..2] # Error: Tuple.[](Range) can only be called with range literals known at compile-time + # + # i = 0..2 + # Foo[i] # Error: Tuple.[](Range) can only be called with range literals known at compile-time + # ``` + def self.[](range : Range) + {% raise "Tuple.[](Range) can only be called with range literals known at compile-time" %} + end + # Returns the element at the given *index* or raises IndexError if out of bounds. # # ``` From 311c0274a8cbe16bf086289dcdd49c1bce3e3e7e Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Wed, 16 Feb 2022 20:49:01 +0800 Subject: [PATCH 2/5] more type docs --- src/named_tuple.cr | 28 ++++++++++++++++++++++++---- src/tuple.cr | 19 +++++++++++++++++++ 2 files changed, 43 insertions(+), 4 deletions(-) diff --git a/src/named_tuple.cr b/src/named_tuple.cr index c9e6483a4ca3..389aa3379d18 100644 --- a/src/named_tuple.cr +++ b/src/named_tuple.cr @@ -17,13 +17,33 @@ # See [`NamedTuple` literals](https://crystal-lang.org/reference/syntax_and_semantics/literals/named_tuple.html) in the language reference. # # The compiler knows what types are in each key, so when indexing a named tuple -# with a symbol literal the compiler will return the value for that key and -# with the expected type, like in the above snippet. Indexing with a symbol -# literal for which there's no key will give a compile-time error. +# with a symbol or string literal the compiler will return the value for that +# key and with the expected type, like in the above snippet. Indexing with a +# symbol or string literal for which there's no key will give a compile-time +# error. # -# Indexing with a symbol that is only known at runtime will return +# Indexing with a symbol or string that is only known at runtime will return # a value whose type is the union of all the types in the named tuple, # and might raise `KeyError`. +# +# Indexing with `#[]?` does not make the return value nilable if the key is +# known to exist: +# +# ``` +# language = {name: "Crystal", year: 2011} +# language[:name]? # => 1 +# typeof(language[:name]?) # => Int32 +# ``` +# +# `NamedTuple`'s own instance classes may also be indexed in a similar manner, +# returning their value types instead: +# +# ``` +# tuple = NamedTuple(name: String, year: Int32) +# tuple[:name] # => String +# tuple["year"] # => Int32 +# tuple[:other]? # => nil +# ``` struct NamedTuple # Creates a named tuple that will contain the given arguments. # diff --git a/src/tuple.cr b/src/tuple.cr index 84b9bbd17d45..d58a5ef7bea2 100644 --- a/src/tuple.cr +++ b/src/tuple.cr @@ -25,6 +25,15 @@ # a value whose type is the union of all the types in the tuple, and might raise # `IndexError`. # +# Indexing with `#[]?` does not make the return value nilable if the index is +# known to be within bounds: +# +# ``` +# tuple = {1, "hello", 'x'} +# tuple[0]? # => 1 +# typeof(tuple[0]?) # => Int32 +# ``` +# # Indexing with a range literal known at compile-time is also allowed, and the # returned value will have the correct sub-tuple type: # @@ -34,6 +43,16 @@ # typeof(sub) # => Tuple(Int32, String) # ``` # +# `Tuple`'s own instance classes may also be indexed in a similar manner, +# returning their element types instead: +# +# ``` +# tuple = Tuple(Int32, String, Char) +# tuple[0] # => Int32 +# tuple[3]? # => nil +# tuple[1..] # => Tuple(String, Char) +# ``` +# # Tuples are the preferred way to return fixed-size multiple return # values because no memory is needed to be allocated for them: # From b86bc0b0185c96af2f0b9fabfbde722ab8c451e7 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Tue, 15 Mar 2022 21:23:46 +0800 Subject: [PATCH 3/5] fixup --- src/named_tuple.cr | 6 ++++-- src/tuple.cr | 2 ++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/named_tuple.cr b/src/named_tuple.cr index 389aa3379d18..15c3bc638de7 100644 --- a/src/named_tuple.cr +++ b/src/named_tuple.cr @@ -65,6 +65,7 @@ struct NamedTuple options {% else %} # explicitly provided type vars + # following `typeof` is needed to access private types {% begin %} { {% for key in T %} @@ -198,10 +199,10 @@ struct NamedTuple # typeof(tuple[key]?) # => (Int32 | String | Nil) # # key = "year" - # tuple[key] # => 2011 + # tuple[key]? # => 2011 # # key = :other - # tuple[key] # => nil + # tuple[key]? # => nil # ``` def []?(key : Symbol | String) fetch(key, nil) @@ -229,6 +230,7 @@ struct NamedTuple # Foo[key]? # => nil # ``` def self.[]?(key : Symbol | String) + # following `typeof` is needed to access private types {% begin %} case key {% for key in T %} diff --git a/src/tuple.cr b/src/tuple.cr index d58a5ef7bea2..d2ca67e263a4 100644 --- a/src/tuple.cr +++ b/src/tuple.cr @@ -116,6 +116,7 @@ struct Tuple args {% else %} # explicitly provided type vars + # following `typeof` is needed to access private types {% begin %} { {% for i in 0...@type.size %} @@ -258,6 +259,7 @@ struct Tuple # Foo[i]? # => nil # ``` def self.[]?(index : Int) + # following `typeof` is needed to access private types {% begin %} case index {% for i in 0...T.size %} From e8e3fa488de346554e5ff50fe98c7e0319355cb2 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Thu, 28 Apr 2022 18:15:41 +0800 Subject: [PATCH 4/5] use `element_type` --- src/named_tuple.cr | 7 ++----- src/tuple.cr | 5 +---- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/src/named_tuple.cr b/src/named_tuple.cr index 3027251a787a..89560487f41b 100644 --- a/src/named_tuple.cr +++ b/src/named_tuple.cr @@ -233,10 +233,7 @@ struct NamedTuple case key {% for key in T %} when {{ key.symbolize }}, {{ key.stringify }} - typeof(begin - x = uninitialized self - x[{{ key.symbolize }}] - end) + typeof(element_type({{ key.symbolize }})) {% end %} end {% end %} @@ -715,6 +712,6 @@ struct NamedTuple # NOTE: there should never be a need to call this method outside the standard library. private macro element_type(key) x = uninitialized self - x[{{ key.symbolize }}] + x[{{ key.id.symbolize }}] end end diff --git a/src/tuple.cr b/src/tuple.cr index 15d006f7e145..16d2675243c8 100644 --- a/src/tuple.cr +++ b/src/tuple.cr @@ -262,10 +262,7 @@ struct Tuple case index {% for i in 0...T.size %} when {{ i }}, {{ i - T.size }} - typeof(begin - v = uninitialized self - v[{{ i }}] - end) + typeof(element_type({{ i }})) {% end %} end {% end %} From d3f09ca1afba5c0a18c9ac7f854aa31721349299 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Tue, 3 May 2022 21:17:50 +0800 Subject: [PATCH 5/5] fixup --- src/named_tuple.cr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/named_tuple.cr b/src/named_tuple.cr index 89560487f41b..2417ce1490b8 100644 --- a/src/named_tuple.cr +++ b/src/named_tuple.cr @@ -168,7 +168,7 @@ struct NamedTuple # # key = :year # Foo[key] # => Int32 - # Foo[key].zero # Error: undefined method '[]' for NamedTuple(name: String, year: Int32).class + # Foo[key].zero # Error: undefined method 'zero' for String.class (compile-time type is (Int32.class | String.class)) # # key = "other" # Foo[key] # raises KeyError