diff --git a/spec/compiler/parser/parser_spec.cr b/spec/compiler/parser/parser_spec.cr index e687500ee2d7..388436e0a6cf 100644 --- a/spec/compiler/parser/parser_spec.cr +++ b/spec/compiler/parser/parser_spec.cr @@ -1276,6 +1276,8 @@ describe "Parser" do it_parses "yield foo do\nend", Yield.new([Call.new(nil, "foo", block: Block.new)] of ASTNode) + it_parses "x.y=(1).to_s", Call.new("x".call, "y=", Call.new(Expressions.new([1.int32] of ASTNode), "to_s")) + assert_syntax_error "return do\nend", "unexpected token: do" %w(def macro class struct module fun alias abstract include extend lib).each do |keyword| diff --git a/src/compiler/crystal/syntax/parser.cr b/src/compiler/crystal/syntax/parser.cr index b5471454a397..e70132dba792 100644 --- a/src/compiler/crystal/syntax/parser.cr +++ b/src/compiler/crystal/syntax/parser.cr @@ -640,10 +640,19 @@ module Crystal next_token if @token.type == :"(" - next_token_skip_space - arg = parse_single_arg - check :")" - next_token + # If we have `f.x=(exp1).a.b.c`, consider it the same as `f.x = (exp1).a.b.c` + # and not as `(f.x = exp1).a.b.c` because a difference in space + # should not make a difference in semantic (#4399) + # The only exception is doing a splat, in which case this can only + # be expanded arguments for the call. + if current_char == '*' + next_token_skip_space + arg = parse_single_arg + check :")" + next_token + else + arg = parse_op_assign_no_control + end else skip_space_or_newline arg = parse_single_arg