From 1d728554be634b2eff143cfb77018e4de7442383 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Sun, 3 Jan 2021 23:22:16 +0100 Subject: [PATCH 1/4] Parse empty array/hash like literals --- spec/compiler/parser/parser_spec.cr | 6 +++++- src/compiler/crystal/syntax/parser.cr | 12 +++++++++--- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/spec/compiler/parser/parser_spec.cr b/spec/compiler/parser/parser_spec.cr index 728e48eb049c..adf1b75debdd 100644 --- a/spec/compiler/parser/parser_spec.cr +++ b/spec/compiler/parser/parser_spec.cr @@ -1371,6 +1371,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)) it_parses "foo(Bar) { 1 }", Call.new(nil, "foo", args: ["Bar".path] of ASTNode, block: Block.new(body: 1.int32)) it_parses "foo Bar { 1 }", Call.new(nil, "foo", args: [ArrayLiteral.new([1.int32] of ASTNode, name: "Bar".path)] of ASTNode) @@ -1637,7 +1639,9 @@ module Crystal assert_syntax_error "def foo(x, **x); end", "duplicated argument 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")) diff --git a/src/compiler/crystal/syntax/parser.cr b/src/compiler/crystal/syntax/parser.cr index af0a11cc5a20..ad8f6bad203d 100644 --- a/src/compiler/crystal/syntax/parser.cr +++ b/src/compiler/crystal/syntax/parser.cr @@ -1256,8 +1256,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 @@ -2356,7 +2362,7 @@ module Crystal if @token.type == :"}" 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 From c7c49ae18f325a75bb45c7e271e6db0df6d1e74e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Sun, 3 Jan 2021 23:27:43 +0100 Subject: [PATCH 2/4] Semantic for empty array/hash literal --- spec/compiler/semantic/array_literal_spec.cr | 15 +++++++++++++++ spec/compiler/semantic/hash_literal_spec.cr | 12 ++++++++++++ src/compiler/crystal/semantic/main_visitor.cr | 7 ++++++- 3 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 spec/compiler/semantic/array_literal_spec.cr create mode 100644 spec/compiler/semantic/hash_literal_spec.cr diff --git a/spec/compiler/semantic/array_literal_spec.cr b/spec/compiler/semantic/array_literal_spec.cr new file mode 100644 index 000000000000..42efa3b038f2 --- /dev/null +++ b/spec/compiler/semantic/array_literal_spec.cr @@ -0,0 +1,15 @@ +require "../../spec_helper" + +describe "array literal" do + it "empty literal" do + assert_type("Array(Int32){}") { array_of int32 } + # An empty array-like literal transforms into a single `.new` call, so it + # technically works with any type that has an argless `.new` method, even if + # it does not respond to `#<<`. + assert_type("String{}") { string } + 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 diff --git a/spec/compiler/semantic/hash_literal_spec.cr b/spec/compiler/semantic/hash_literal_spec.cr new file mode 100644 index 000000000000..78bea3b5f5a5 --- /dev/null +++ b/spec/compiler/semantic/hash_literal_spec.cr @@ -0,0 +1,12 @@ +require "../../spec_helper" + +describe "hash literal" do + it "empty literal" do + assert_type("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 diff --git a/src/compiler/crystal/semantic/main_visitor.cr b/src/compiler/crystal/semantic/main_visitor.cr index 3c1f0d12ba95..abccd3f38374 100644 --- a/src/compiler/crystal/semantic/main_visitor.cr +++ b/src/compiler/crystal/semantic/main_visitor.cr @@ -2994,7 +2994,12 @@ module Crystal case type when GenericClassType generic_type = TypeNode.new(type).at(node.location) - type_of = TypeOf.new(node.elements).at(node.location) + + if node.elements.empty? + type_of = [] of ASTNode + else + type_of = TypeOf.new(node.elements).at(node.location) + end generic = Generic.new(generic_type, type_of).at(node.location) From 57c42d467d8ed877fc019371585918d37871f0c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Mon, 4 Jan 2021 13:39:45 +0100 Subject: [PATCH 3/4] Error for unsupported type --- spec/compiler/semantic/array_literal_spec.cr | 11 ++++++----- spec/compiler/semantic/hash_literal_spec.cr | 2 +- src/compiler/crystal/semantic/main_visitor.cr | 9 +++++++++ 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/spec/compiler/semantic/array_literal_spec.cr b/spec/compiler/semantic/array_literal_spec.cr index 42efa3b038f2..972f2282e445 100644 --- a/spec/compiler/semantic/array_literal_spec.cr +++ b/spec/compiler/semantic/array_literal_spec.cr @@ -2,11 +2,12 @@ require "../../spec_helper" describe "array literal" do it "empty literal" do - assert_type("Array(Int32){}") { array_of int32 } - # An empty array-like literal transforms into a single `.new` call, so it - # technically works with any type that has an argless `.new` method, even if - # it does not respond to `#<<`. - assert_type("String{}") { string } + 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 diff --git a/spec/compiler/semantic/hash_literal_spec.cr b/spec/compiler/semantic/hash_literal_spec.cr index 78bea3b5f5a5..daa45d251169 100644 --- a/spec/compiler/semantic/hash_literal_spec.cr +++ b/spec/compiler/semantic/hash_literal_spec.cr @@ -2,7 +2,7 @@ require "../../spec_helper" describe "hash literal" do it "empty literal" do - assert_type("Hash(Int32, Int32){}") { hash_of int32, int32 } + 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 diff --git a/src/compiler/crystal/semantic/main_visitor.cr b/src/compiler/crystal/semantic/main_visitor.cr index abccd3f38374..b9cd3d46d7a3 100644 --- a/src/compiler/crystal/semantic/main_visitor.cr +++ b/src/compiler/crystal/semantic/main_visitor.cr @@ -3003,10 +3003,19 @@ module Crystal generic = Generic.new(generic_type, type_of).at(node.location) + concrete_type = nil 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" + 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 From 9cef8ca98301af67a715f892c6494e5fde06199e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Sun, 24 Jan 2021 22:36:15 +0100 Subject: [PATCH 4/4] crystal tool format --- src/compiler/crystal/semantic/main_visitor.cr | 1 - 1 file changed, 1 deletion(-) diff --git a/src/compiler/crystal/semantic/main_visitor.cr b/src/compiler/crystal/semantic/main_visitor.cr index b9cd3d46d7a3..8523b59fe528 100644 --- a/src/compiler/crystal/semantic/main_visitor.cr +++ b/src/compiler/crystal/semantic/main_visitor.cr @@ -3009,7 +3009,6 @@ module Crystal if node.elements.empty? && !(type.has_def?("<<") || type.has_def?("[]=")) node.raise "Type #{type} does not support array-like or hash-like literal" end - # Nothing else if node.elements.empty? && !(type.has_def?("<<") || type.has_def?("[]="))