From f3f8fe29e67cd03d63de9bd6e8400c915c361547 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?fn=20=E2=8C=83=20=E2=8C=A5?= <70830482+FnControlOption@users.noreply.github.com> Date: Tue, 16 Jul 2024 14:03:41 -0700 Subject: [PATCH 1/8] Add `Crystal::Call#dot_location` --- spec/compiler/parser/parser_spec.cr | 24 ++++++++++++++++++++++++ src/compiler/crystal/syntax/ast.cr | 1 + src/compiler/crystal/syntax/parser.cr | 5 +++++ 3 files changed, 30 insertions(+) diff --git a/spec/compiler/parser/parser_spec.cr b/spec/compiler/parser/parser_spec.cr index 22e9c5feb385..1dc65f7e0be5 100644 --- a/spec/compiler/parser/parser_spec.cr +++ b/spec/compiler/parser/parser_spec.cr @@ -2821,5 +2821,29 @@ module Crystal node = Parser.parse(source).as(Annotation).path node_source(source, node).should eq("::Foo") end + + it "sets correct location of call dot" do + parser = Parser.new("a.b") + node = parser.parse.as(Call) + dot_location = node.dot_location.not_nil! + dot_location.line_number.should eq(1) + dot_location.column_number.should eq(2) + end + + it "sets correct location of call dot in assignment" do + parser = Parser.new("a.b = c") + node = parser.parse.as(Call) + dot_location = node.dot_location.not_nil! + dot_location.line_number.should eq(1) + dot_location.column_number.should eq(2) + end + + it "sets correct location of call dot in operator assignment" do + parser = Parser.new("a.b += c") + node = parser.parse.as(OpAssign).target.as(Call) + dot_location = node.dot_location.not_nil! + dot_location.line_number.should eq(1) + dot_location.column_number.should eq(2) + end end end diff --git a/src/compiler/crystal/syntax/ast.cr b/src/compiler/crystal/syntax/ast.cr index f6d314371034..9641da81f7b6 100644 --- a/src/compiler/crystal/syntax/ast.cr +++ b/src/compiler/crystal/syntax/ast.cr @@ -647,6 +647,7 @@ module Crystal property block : Block? property block_arg : ASTNode? property named_args : Array(NamedArgument)? + property dot_location : Location? property name_location : Location? @name_size = -1 property doc : String? diff --git a/src/compiler/crystal/syntax/parser.cr b/src/compiler/crystal/syntax/parser.cr index bd7c67a975b8..2c2e4f0e6383 100644 --- a/src/compiler/crystal/syntax/parser.cr +++ b/src/compiler/crystal/syntax/parser.cr @@ -678,6 +678,8 @@ module Crystal break end when .op_period? + dot_location = @token.location + check_void_value atomic, location @wants_regex = false @@ -761,6 +763,7 @@ module Crystal end atomic = Call.new(atomic, "#{name}=", arg).at(location).at_end(end_location) + atomic.dot_location = dot_location atomic.name_location = name_location next when .assignment_operator? @@ -769,6 +772,7 @@ module Crystal next_token_skip_space_or_newline value = parse_op_assign call = Call.new(atomic, name).at(location) + call.dot_location = dot_location call.name_location = name_location atomic = OpAssign.new(call, method, value).at(location) atomic.name_location = op_name_location @@ -790,6 +794,7 @@ module Crystal block = parse_block(block, stop_on_do: @stop_on_do) atomic = Call.new atomic, name, (args || [] of ASTNode), block, block_arg, named_args atomic.has_parentheses = has_parentheses + atomic.dot_location = dot_location atomic.name_location = name_location atomic.end_location = block.try(&.end_location) || call_args.try(&.end_location) || end_location atomic.at(location) From f05e9b6567921bce0d44484aa51079d84de6cced Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?fn=20=E2=8C=83=20=E2=8C=A5?= <70830482+FnControlOption@users.noreply.github.com> Date: Tue, 16 Jul 2024 14:12:34 -0700 Subject: [PATCH 2/8] Disallow assignments like `a.[] 0 = 1` --- spec/compiler/parser/parser_spec.cr | 3 +++ spec/support/syntax.cr | 4 ++-- src/compiler/crystal/syntax/parser.cr | 4 +++- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/spec/compiler/parser/parser_spec.cr b/spec/compiler/parser/parser_spec.cr index 1dc65f7e0be5..a67de2b76188 100644 --- a/spec/compiler/parser/parser_spec.cr +++ b/spec/compiler/parser/parser_spec.cr @@ -276,6 +276,9 @@ module Crystal assert_syntax_error "a.b() += 1" assert_syntax_error "a.[]() += 1" + assert_syntax_error "a.[] 0 = 1" + assert_syntax_error "a.[] 0 += 1" + it_parses "def foo\n1\nend", Def.new("foo", body: 1.int32) it_parses "def downto(n)\n1\nend", Def.new("downto", ["n".arg], 1.int32) it_parses "def foo ; 1 ; end", Def.new("foo", body: 1.int32) diff --git a/spec/support/syntax.cr b/spec/support/syntax.cr index e1fd8f43d951..a6fe6286d11b 100644 --- a/spec/support/syntax.cr +++ b/spec/support/syntax.cr @@ -133,8 +133,8 @@ class Crystal::ASTNode end end -def assert_syntax_error(str, message = nil, line = nil, column = nil, metafile = __FILE__, metaline = __LINE__, metaendline = __END_LINE__) - it "says syntax error on #{str.inspect}", metafile, metaline, metaendline do +def assert_syntax_error(str, message = nil, line = nil, column = nil, metafile = __FILE__, metaline = __LINE__, metaendline = __END_LINE__, *, focus : Bool = false) + it "says syntax error on #{str.inspect}", metafile, metaline, metaendline, focus: focus do begin parse str fail "Expected SyntaxException to be raised", metafile, metaline diff --git a/src/compiler/crystal/syntax/parser.cr b/src/compiler/crystal/syntax/parser.cr index 2c2e4f0e6383..6c0cf1870f88 100644 --- a/src/compiler/crystal/syntax/parser.cr +++ b/src/compiler/crystal/syntax/parser.cr @@ -6190,7 +6190,9 @@ module Crystal when Var, InstanceVar, ClassVar, Path, Global, Underscore true when Call - !node.has_parentheses? && ((node.obj.nil? && node.args.empty? && node.block.nil?) || node.name == "[]") + return false if node.has_parentheses? + return true if node.obj.nil? && node.args.empty? && node.block.nil? + node.name == "[]" && node.dot_location.nil? else false end From 3a2ccfc8200dffbe0408dae7b6f91b0eb70e1f33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?fn=20=E2=8C=83=20=E2=8C=A5?= <70830482+FnControlOption@users.noreply.github.com> Date: Tue, 16 Jul 2024 14:22:23 -0700 Subject: [PATCH 3/8] Disallow assignments like `a b: 0 = 1` --- spec/compiler/parser/parser_spec.cr | 1 + src/compiler/crystal/syntax/parser.cr | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/spec/compiler/parser/parser_spec.cr b/spec/compiler/parser/parser_spec.cr index a67de2b76188..7e2f45d632dd 100644 --- a/spec/compiler/parser/parser_spec.cr +++ b/spec/compiler/parser/parser_spec.cr @@ -278,6 +278,7 @@ module Crystal assert_syntax_error "a.[] 0 = 1" assert_syntax_error "a.[] 0 += 1" + assert_syntax_error "a b: 0 = 1" it_parses "def foo\n1\nend", Def.new("foo", body: 1.int32) it_parses "def downto(n)\n1\nend", Def.new("downto", ["n".arg], 1.int32) diff --git a/src/compiler/crystal/syntax/parser.cr b/src/compiler/crystal/syntax/parser.cr index 6c0cf1870f88..2882ed7dffb8 100644 --- a/src/compiler/crystal/syntax/parser.cr +++ b/src/compiler/crystal/syntax/parser.cr @@ -6191,7 +6191,7 @@ module Crystal true when Call return false if node.has_parentheses? - return true if node.obj.nil? && node.args.empty? && node.block.nil? + return true if node.obj.nil? && node.args.empty? && node.named_args.nil? && node.block.nil? node.name == "[]" && node.dot_location.nil? else false From fe6980eac8244de3d0b05e2476d4cad6e6a85fd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?fn=20=E2=8C=83=20=E2=8C=A5?= <70830482+FnControlOption@users.noreply.github.com> Date: Sat, 20 Jul 2024 10:20:15 -0700 Subject: [PATCH 4/8] Replace `dot_location` with `args_in_brackets` --- spec/compiler/parser/parser_spec.cr | 28 +++++++++++---------------- src/compiler/crystal/syntax/ast.cr | 2 +- src/compiler/crystal/syntax/parser.cr | 13 +++++-------- 3 files changed, 17 insertions(+), 26 deletions(-) diff --git a/spec/compiler/parser/parser_spec.cr b/spec/compiler/parser/parser_spec.cr index 7e2f45d632dd..69ece5f56fab 100644 --- a/spec/compiler/parser/parser_spec.cr +++ b/spec/compiler/parser/parser_spec.cr @@ -204,6 +204,8 @@ module Crystal it_parses "a = 1", Assign.new("a".var, 1.int32) it_parses "a = b = 2", Assign.new("a".var, Assign.new("b".var, 2.int32)) + it_parses "a[] = 1", Call.new("a".call, "[]=", 1.int32) + it_parses "a.[] = 1", Call.new("a".call, "[]=", 1.int32) it_parses "a, b = 1, 2", MultiAssign.new(["a".var, "b".var] of ASTNode, [1.int32, 2.int32] of ASTNode) it_parses "a, b = 1", MultiAssign.new(["a".var, "b".var] of ASTNode, [1.int32] of ASTNode) @@ -528,11 +530,15 @@ module Crystal it_parses "foo &.+(2)", Call.new(nil, "foo", block: Block.new([Var.new("__arg0")], Call.new(Var.new("__arg0"), "+", 2.int32))) it_parses "foo &.bar.baz", Call.new(nil, "foo", block: Block.new([Var.new("__arg0")], Call.new(Call.new(Var.new("__arg0"), "bar"), "baz"))) it_parses "foo(&.bar.baz)", Call.new(nil, "foo", block: Block.new([Var.new("__arg0")], Call.new(Call.new(Var.new("__arg0"), "bar"), "baz"))) + it_parses "foo &.block[]", Call.new(nil, "foo", block: Block.new([Var.new("__arg0")], Call.new(Call.new(Var.new("__arg0"), "block"), "[]"))) it_parses "foo &.block[0]", Call.new(nil, "foo", block: Block.new([Var.new("__arg0")], Call.new(Call.new(Var.new("__arg0"), "block"), "[]", 0.int32))) it_parses "foo &.block=(0)", Call.new(nil, "foo", block: Block.new([Var.new("__arg0")], Call.new(Var.new("__arg0"), "block=", 0.int32))) it_parses "foo &.block = 0", Call.new(nil, "foo", block: Block.new([Var.new("__arg0")], Call.new(Var.new("__arg0"), "block=", 0.int32))) + it_parses "foo &.block[] = 1", Call.new(nil, "foo", block: Block.new([Var.new("__arg0")], Call.new(Call.new(Var.new("__arg0"), "block"), "[]=", 1.int32))) it_parses "foo &.block[0] = 1", Call.new(nil, "foo", block: Block.new([Var.new("__arg0")], Call.new(Call.new(Var.new("__arg0"), "block"), "[]=", 0.int32, 1.int32))) + it_parses "foo &.[]", Call.new(nil, "foo", block: Block.new([Var.new("__arg0")], Call.new(Var.new("__arg0"), "[]"))) it_parses "foo &.[0]", Call.new(nil, "foo", block: Block.new([Var.new("__arg0")], Call.new(Var.new("__arg0"), "[]", 0.int32))) + it_parses "foo &.[] = 1", Call.new(nil, "foo", block: Block.new([Var.new("__arg0")], Call.new(Var.new("__arg0"), "[]=", 1.int32))) it_parses "foo &.[0] = 1", Call.new(nil, "foo", block: Block.new([Var.new("__arg0")], Call.new(Var.new("__arg0"), "[]=", 0.int32, 1.int32))) it_parses "foo(&.is_a?(T))", Call.new(nil, "foo", block: Block.new([Var.new("__arg0")], IsA.new(Var.new("__arg0"), "T".path))) it_parses "foo(&.!)", Call.new(nil, "foo", block: Block.new([Var.new("__arg0")], Not.new(Var.new("__arg0")))) @@ -2826,28 +2832,16 @@ module Crystal node_source(source, node).should eq("::Foo") end - it "sets correct location of call dot" do + it "sets args_in_brackets to false for `a.b`" do parser = Parser.new("a.b") node = parser.parse.as(Call) - dot_location = node.dot_location.not_nil! - dot_location.line_number.should eq(1) - dot_location.column_number.should eq(2) + node.args_in_brackets?.should be_false end - it "sets correct location of call dot in assignment" do - parser = Parser.new("a.b = c") + it "sets args_in_brackets to true for `a[b]`" do + parser = Parser.new("a[b]") node = parser.parse.as(Call) - dot_location = node.dot_location.not_nil! - dot_location.line_number.should eq(1) - dot_location.column_number.should eq(2) - end - - it "sets correct location of call dot in operator assignment" do - parser = Parser.new("a.b += c") - node = parser.parse.as(OpAssign).target.as(Call) - dot_location = node.dot_location.not_nil! - dot_location.line_number.should eq(1) - dot_location.column_number.should eq(2) + node.args_in_brackets?.should be_true end end end diff --git a/src/compiler/crystal/syntax/ast.cr b/src/compiler/crystal/syntax/ast.cr index 9641da81f7b6..9ccd8dda1f69 100644 --- a/src/compiler/crystal/syntax/ast.cr +++ b/src/compiler/crystal/syntax/ast.cr @@ -647,13 +647,13 @@ module Crystal property block : Block? property block_arg : ASTNode? property named_args : Array(NamedArgument)? - property dot_location : Location? property name_location : Location? @name_size = -1 property doc : String? property visibility = Visibility::Public property? global : Bool property? expansion = false + property? args_in_brackets = false property? has_parentheses = false def initialize(@obj, @name, @args = [] of ASTNode, @block = nil, @block_arg = nil, @named_args = nil, @global : Bool = false) diff --git a/src/compiler/crystal/syntax/parser.cr b/src/compiler/crystal/syntax/parser.cr index 2882ed7dffb8..98856f2e7692 100644 --- a/src/compiler/crystal/syntax/parser.cr +++ b/src/compiler/crystal/syntax/parser.cr @@ -678,8 +678,6 @@ module Crystal break end when .op_period? - dot_location = @token.location - check_void_value atomic, location @wants_regex = false @@ -763,7 +761,6 @@ module Crystal end atomic = Call.new(atomic, "#{name}=", arg).at(location).at_end(end_location) - atomic.dot_location = dot_location atomic.name_location = name_location next when .assignment_operator? @@ -772,7 +769,6 @@ module Crystal next_token_skip_space_or_newline value = parse_op_assign call = Call.new(atomic, name).at(location) - call.dot_location = dot_location call.name_location = name_location atomic = OpAssign.new(call, method, value).at(location) atomic.name_location = op_name_location @@ -794,7 +790,6 @@ module Crystal block = parse_block(block, stop_on_do: @stop_on_do) atomic = Call.new atomic, name, (args || [] of ASTNode), block, block_arg, named_args atomic.has_parentheses = has_parentheses - atomic.dot_location = dot_location atomic.name_location = name_location atomic.end_location = block.try(&.end_location) || call_args.try(&.end_location) || end_location atomic.at(location) @@ -853,7 +848,8 @@ module Crystal atomic = Call.new(atomic, method_name, (args || [] of ASTNode), block, block_arg, named_args).at(location) atomic.name_location = name_location atomic.end_location = end_location - atomic.name_size = 0 if atomic.is_a?(Call) + atomic.name_size = 0 + atomic.args_in_brackets = true atomic else break @@ -6191,8 +6187,9 @@ module Crystal true when Call return false if node.has_parentheses? - return true if node.obj.nil? && node.args.empty? && node.named_args.nil? && node.block.nil? - node.name == "[]" && node.dot_location.nil? + no_args = node.args.empty? && node.named_args.nil? && node.block.nil? + return true if node.obj.nil? && no_args + node.name == "[]" && (node.args_in_brackets? || no_args) else false end From 9911d5244e7fec1a7f905b240e4e832892411eeb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?fn=20=E2=8C=83=20=E2=8C=A5?= <70830482+FnControlOption@users.noreply.github.com> Date: Sat, 20 Jul 2024 10:26:19 -0700 Subject: [PATCH 5/8] Disallow `foo &.[0]? = 1` --- spec/compiler/parser/parser_spec.cr | 7 +++++++ src/compiler/crystal/syntax/parser.cr | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/spec/compiler/parser/parser_spec.cr b/spec/compiler/parser/parser_spec.cr index 69ece5f56fab..7bdd63a48d75 100644 --- a/spec/compiler/parser/parser_spec.cr +++ b/spec/compiler/parser/parser_spec.cr @@ -2236,6 +2236,13 @@ module Crystal assert_syntax_error "lib Foo%end", %(unexpected token: "%") + assert_syntax_error "foo[0]? = 1" + assert_syntax_error "foo[0]? += 1" + assert_syntax_error "foo.[0]? = 1" + assert_syntax_error "foo.[0]? += 1" + assert_syntax_error "foo &.[0]? = 1" + assert_syntax_error "foo &.[0]? += 1" + describe "end locations" do assert_end_location "nil" assert_end_location "false" diff --git a/src/compiler/crystal/syntax/parser.cr b/src/compiler/crystal/syntax/parser.cr index 98856f2e7692..ab5a4423a4e6 100644 --- a/src/compiler/crystal/syntax/parser.cr +++ b/src/compiler/crystal/syntax/parser.cr @@ -1623,7 +1623,7 @@ module Crystal elsif @token.type.op_lsquare? call = parse_atomic_method_suffix obj, location - if @token.type.op_eq? && call.is_a?(Call) + if @token.type.op_eq? && call.is_a?(Call) && call.name == "[]" next_token_skip_space exp = parse_op_assign call.name = "#{call.name}=" From d556c7c0dce46acd8e276e7b143328eb91bedf20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?fn=20=E2=8C=83=20=E2=8C=A5?= <70830482+FnControlOption@users.noreply.github.com> Date: Sat, 20 Jul 2024 12:12:37 -0700 Subject: [PATCH 6/8] Disallow `foo.[]? = 1` and `foo.[]? += 1` --- spec/compiler/parser/parser_spec.cr | 2 ++ src/compiler/crystal/syntax/parser.cr | 14 +++++++++++--- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/spec/compiler/parser/parser_spec.cr b/spec/compiler/parser/parser_spec.cr index 7bdd63a48d75..2f3495debe10 100644 --- a/spec/compiler/parser/parser_spec.cr +++ b/spec/compiler/parser/parser_spec.cr @@ -2236,6 +2236,8 @@ module Crystal assert_syntax_error "lib Foo%end", %(unexpected token: "%") + assert_syntax_error "foo.[]? = 1" + assert_syntax_error "foo.[]? += 1" assert_syntax_error "foo[0]? = 1" assert_syntax_error "foo[0]? += 1" assert_syntax_error "foo.[0]? = 1" diff --git a/src/compiler/crystal/syntax/parser.cr b/src/compiler/crystal/syntax/parser.cr index ab5a4423a4e6..9ac03ba45667 100644 --- a/src/compiler/crystal/syntax/parser.cr +++ b/src/compiler/crystal/syntax/parser.cr @@ -735,6 +735,9 @@ module Crystal case @token.type when .op_eq? + atomic = Call.new(atomic, name) + unexpected_token unless can_be_assigned?(atomic) + # Rewrite 'f.x = arg' as f.x=(arg) next_token @@ -760,15 +763,20 @@ module Crystal end_location = arg.end_location end - atomic = Call.new(atomic, "#{name}=", arg).at(location).at_end(end_location) + atomic.at(location).at_end(end_location) + atomic.name = "#{name}=" + atomic.args = [arg] of ASTNode atomic.name_location = name_location next when .assignment_operator? + call = Call.new(atomic, name) + unexpected_token unless can_be_assigned?(call) + op_name_location = @token.location method = @token.type.to_s.byte_slice(0, @token.type.to_s.size - 1) next_token_skip_space_or_newline value = parse_op_assign - call = Call.new(atomic, name).at(location) + call.at(location) call.name_location = name_location atomic = OpAssign.new(call, method, value).at(location) atomic.name_location = op_name_location @@ -6188,7 +6196,7 @@ module Crystal when Call return false if node.has_parentheses? no_args = node.args.empty? && node.named_args.nil? && node.block.nil? - return true if node.obj.nil? && no_args + return true if Lexer.ident?(node.name) && no_args node.name == "[]" && (node.args_in_brackets? || no_args) else false From 903e4b9dc97f9d036b8af3c7081a9b89cefb2114 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?fn=20=E2=8C=83=20=E2=8C=A5?= <70830482+FnControlOption@users.noreply.github.com> Date: Sat, 20 Jul 2024 12:27:32 -0700 Subject: [PATCH 7/8] Disallow `foo &.[](0) = 1` --- spec/compiler/parser/parser_spec.cr | 11 +++++++++++ src/compiler/crystal/syntax/parser.cr | 2 ++ 2 files changed, 13 insertions(+) diff --git a/spec/compiler/parser/parser_spec.cr b/spec/compiler/parser/parser_spec.cr index 2f3495debe10..30ec21e1466f 100644 --- a/spec/compiler/parser/parser_spec.cr +++ b/spec/compiler/parser/parser_spec.cr @@ -2245,6 +2245,17 @@ module Crystal assert_syntax_error "foo &.[0]? = 1" assert_syntax_error "foo &.[0]? += 1" + assert_syntax_error "foo &.[]?=(1)" + assert_syntax_error "foo &.[]? = 1" + assert_syntax_error "foo &.[]? 0 =(1)" + assert_syntax_error "foo &.[]? 0 = 1" + assert_syntax_error "foo &.[]?(0)=(1)" + assert_syntax_error "foo &.[]?(0) = 1" + assert_syntax_error "foo &.[] 0 =(1)" + assert_syntax_error "foo &.[] 0 = 1" + assert_syntax_error "foo &.[](0)=(1)" + assert_syntax_error "foo &.[](0) = 1" + describe "end locations" do assert_end_location "nil" assert_end_location "false" diff --git a/src/compiler/crystal/syntax/parser.cr b/src/compiler/crystal/syntax/parser.cr index 9ac03ba45667..8dc5b8c1dc14 100644 --- a/src/compiler/crystal/syntax/parser.cr +++ b/src/compiler/crystal/syntax/parser.cr @@ -1652,6 +1652,8 @@ module Crystal call = call.as(Call) if @token.type.op_eq? + unexpected_token unless can_be_assigned?(call) + next_token_skip_space if @token.type.op_lparen? next_token_skip_space From 74ff652c7711c98eaee4894dafb24c4787e69998 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?fn=20=E2=8C=83=20=E2=8C=A5?= <70830482+FnControlOption@users.noreply.github.com> Date: Sat, 20 Jul 2024 12:38:11 -0700 Subject: [PATCH 8/8] Disallow `foo &.bar.[](0) = 1` --- spec/compiler/parser/parser_spec.cr | 5 +++++ src/compiler/crystal/syntax/parser.cr | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/spec/compiler/parser/parser_spec.cr b/spec/compiler/parser/parser_spec.cr index 30ec21e1466f..f719e5c41b06 100644 --- a/spec/compiler/parser/parser_spec.cr +++ b/spec/compiler/parser/parser_spec.cr @@ -2256,6 +2256,11 @@ module Crystal assert_syntax_error "foo &.[](0)=(1)" assert_syntax_error "foo &.[](0) = 1" + assert_syntax_error "foo &.bar.[] 0 =(1)" + assert_syntax_error "foo &.bar.[] 0 = 1" + assert_syntax_error "foo &.bar.[](0)=(1)" + assert_syntax_error "foo &.bar.[](0) = 1" + describe "end locations" do assert_end_location "nil" assert_end_location "false" diff --git a/src/compiler/crystal/syntax/parser.cr b/src/compiler/crystal/syntax/parser.cr index 8dc5b8c1dc14..7153e0ede247 100644 --- a/src/compiler/crystal/syntax/parser.cr +++ b/src/compiler/crystal/syntax/parser.cr @@ -1631,7 +1631,7 @@ module Crystal elsif @token.type.op_lsquare? call = parse_atomic_method_suffix obj, location - if @token.type.op_eq? && call.is_a?(Call) && call.name == "[]" + if @token.type.op_eq? && call.is_a?(Call) && can_be_assigned?(call) next_token_skip_space exp = parse_op_assign call.name = "#{call.name}=" @@ -1671,7 +1671,7 @@ module Crystal else call = parse_atomic_method_suffix call, location - if @token.type.op_eq? && call.is_a?(Call) && call.name == "[]" + if @token.type.op_eq? && call.is_a?(Call) && can_be_assigned?(call) next_token_skip_space exp = parse_op_assign call.name = "#{call.name}="