Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Parse empty array/hash like literals #10192

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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion spec/compiler/parser/parser_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -1473,6 +1473,8 @@ module Crystal

it_parses "Set {1, 2, 3}", ArrayLiteral.new([1.int32, 2.int32, 3.int32] of ASTNode, name: "Set".path)
it_parses "Set(Int32) {1, 2, 3}", ArrayLiteral.new([1.int32, 2.int32, 3.int32] of ASTNode, name: Generic.new("Set".path, ["Int32".path] of ASTNode))
it_parses "Set {}", ArrayLiteral.new([] of ASTNode, name: "Set".path)
it_parses "Set(Int32) {}", ArrayLiteral.new([] of ASTNode, name: Generic.new("Set".path, ["Int32".path] of ASTNode))

describe "single splats inside container literals" do
it_parses "{*1}", TupleLiteral.new([1.int32.splat] of ASTNode)
Expand Down Expand Up @@ -1780,7 +1782,9 @@ module Crystal
assert_syntax_error "def foo(x, **x); end", "duplicated def parameter name: x"

assert_syntax_error "Set {1, 2, 3} of Int32"
assert_syntax_error "Hash {foo: 1} of Int32 => Int32"
assert_syntax_error "Hash {foo => 1} of Int32 => Int32"
assert_syntax_error "Set {} of Int32"
assert_syntax_error "Hash {} of Int32 => Int32"
assert_syntax_error "enum Foo < UInt16; end"
assert_syntax_error "foo(1 2)"
assert_syntax_error %(foo("bar" "baz"))
Expand Down
16 changes: 16 additions & 0 deletions spec/compiler/semantic/array_literal_spec.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
require "../../spec_helper"

describe "array literal" do
it "empty literal" do
assert_type("class Array(T); def <<(t : T); end; end; Array(Int32){}") { array_of int32 }
end

it "non-supported type" do
assert_error("String{}", "Type String does not support array-like or hash-like literal")
assert_error("class Foo(T); end; Foo(Int32){}", "Type Foo(Int32) does not support array-like or hash-like literal")
end

it "empty literal missing generic arguments" do
assert_error("Array{}", "wrong number of type vars for Array(T) (given 0, expected 1)")
end
end
12 changes: 12 additions & 0 deletions spec/compiler/semantic/hash_literal_spec.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
require "../../spec_helper"

describe "hash literal" do
it "empty literal" do
assert_type("class Hash(K, V); def []=(k : K, v : V); end; end; Hash(Int32, Int32){}") { hash_of int32, int32 }
end

it "empty literal missing generic arguments" do
assert_error("Hash{}", "wrong number of type vars for Hash(K, V) (given 0, expected 2)")
assert_error("Hash(Int32){}", "wrong number of type vars for Hash(K, V) (given 1, expected 2)")
end
end
16 changes: 15 additions & 1 deletion src/compiler/crystal/semantic/main_visitor.cr
Original file line number Diff line number Diff line change
Expand Up @@ -2923,12 +2923,26 @@ module Crystal
case type
when GenericClassType
generic_type = TypeNode.new(type).at(node.location)
type_of = @program.literal_expander.typeof_exp(node)

if node.elements.empty?
type_of = [] of ASTNode
else
type_of = @program.literal_expander.typeof_exp(node)
end

generic = Generic.new(generic_type, type_of).at(node.location)

node.name = generic
when GenericClassInstanceType
if node.elements.empty? && !(type.has_def?("<<") || type.has_def?("[]="))
node.raise "Type #{type} does not support array-like or hash-like literal"
Copy link
Member

Choose a reason for hiding this comment

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

💭 Optional! The error could be a bit more explicit, like saying:

does not support array-like literal (it has no << method) or hash-like literal (it has no []= method)

end
# Nothing
else
if node.elements.empty? && !(type.has_def?("<<") || type.has_def?("[]="))
node.raise "Type #{type} does not support array-like or hash-like literal"
end

node.name = TypeNode.new(name.type).at(node.location)
end

Expand Down
12 changes: 9 additions & 3 deletions src/compiler/crystal/syntax/parser.cr
Original file line number Diff line number Diff line change
Expand Up @@ -1266,8 +1266,14 @@ module Crystal
ary = ArrayLiteral.new(tuple_or_hash.elements, name: type).at(tuple_or_hash.location)
return ary
when HashLiteral
tuple_or_hash.name = type
return tuple_or_hash
if tuple_or_hash.entries.empty?
# an empty literal is ambiguous, we're picking ArrayLiteral for simplicity
ary = ArrayLiteral.new([] of ASTNode, name: type).at(tuple_or_hash.location)
return ary
else
tuple_or_hash.name = type
return tuple_or_hash
end
else
raise "BUG: tuple_or_hash should be tuple or hash, not #{tuple_or_hash}"
end
Expand Down Expand Up @@ -2400,7 +2406,7 @@ module Crystal
if @token.type.op_rcurly?
end_location = token_end_location
next_token_skip_space
new_hash_literal([] of HashLiteral::Entry, line, column, end_location)
new_hash_literal([] of HashLiteral::Entry, line, column, end_location, allow_of: allow_of)
else
if named_tuple_start?
unless allow_of
Expand Down