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

Formatter: Handle leading tuple literals in multi-expression return/break/next properly #10597

Merged
Show file tree
Hide file tree
Changes from 4 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
10 changes: 9 additions & 1 deletion spec/compiler/formatter/formatter_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -342,7 +342,15 @@ describe Crystal::Formatter do
assert_format "#{keyword} 1", "#{keyword} 1"
assert_format "#{keyword}( 1 , 2 )", "#{keyword}(1, 2)"
assert_format "#{keyword} 1 , 2", "#{keyword} 1, 2"
assert_format "#{keyword} { 1 , 2 }", "#{keyword} {1, 2}" unless keyword == "yield"

unless keyword == "yield"
assert_format "#{keyword} { 1 , 2 }", "#{keyword} {1, 2}"
assert_format "#{keyword} {1, 2}, 3"
assert_format "#{keyword} 1, {2, 3}"
assert_format "#{keyword} {1, 2}, {3, 4}"
assert_format "#{keyword} { {1, 2}, {3, 4} }"
assert_format "#{keyword} { {1, 2}, {3, 4} }, 5"
end
end

assert_format "yield 1\n2", "yield 1\n2"
Expand Down
20 changes: 15 additions & 5 deletions src/compiler/crystal/syntax/lexer.cr
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ module Crystal
def initialize(string, string_pool : StringPool? = nil)
@reader = Char::Reader.new(string)
@token = Token.new
@temp_token = Token.new
@line_number = 1
@column_number = 1
@filename = ""
Expand Down Expand Up @@ -2524,14 +2525,23 @@ module Crystal
@token
end

def lookahead
old_pos = @reader.pos
old_line_number, old_column_number = @line_number, @column_number
def lookahead(preserve_token_on_fail = false)
old_pos, old_line, old_column = current_pos, @line_number, @column_number
@temp_token.copy_from(@token) if preserve_token_on_fail

result = yield
unless result
@reader.pos = old_pos
@line_number, @column_number = old_line_number, old_column_number
self.current_pos, @line_number, @column_number = old_pos, old_line, old_column
@token.copy_from(@temp_token) if preserve_token_on_fail
end
result
end

def peek_ahead
result = uninitialized typeof(yield)
lookahead(preserve_token_on_fail: true) do
result = yield
nil
end
result
straight-shoota marked this conversation as resolved.
Show resolved Hide resolved
end
Expand Down
43 changes: 12 additions & 31 deletions src/compiler/crystal/syntax/parser.cr
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ module Crystal

def initialize(str, string_pool : StringPool? = nil, @def_vars = [Set(String).new])
super(str, string_pool)
@temp_token = Token.new
@unclosed_stack = [] of Unclosed
@calls_super = false
@calls_initialize = false
Expand Down Expand Up @@ -624,12 +623,7 @@ module Crystal
end

# Allow '.' after newline for chaining calls
old_pos, old_line, old_column = current_pos, @line_number, @column_number
@temp_token.copy_from @token
next_token_skip_space_or_newline
unless @token.type == :"."
self.current_pos, @line_number, @column_number = old_pos, old_line, old_column
@token.copy_from @temp_token
unless lookahead(preserve_token_on_fail: true) { next_token_skip_space_or_newline; @token.type == :"." }
break
end
when :"."
Expand Down Expand Up @@ -965,21 +959,10 @@ module Crystal
location = @token.location
var = Var.new(@token.to_s).at(location)

old_pos, old_line, old_column = current_pos, @line_number, @column_number
@temp_token.copy_from(@token)

next_token_skip_space

if @token.type == :"="
@token.copy_from(@temp_token)
self.current_pos, @line_number, @column_number = old_pos, old_line, old_column

if peek_ahead { next_token_skip_space; @token.type == :"=" }
push_var var
node_and_next_token var
else
@token.copy_from(@temp_token)
self.current_pos, @line_number, @column_number = old_pos, old_line, old_column

node_and_next_token Global.new(var.name).at(location)
end
when :GLOBAL_MATCH_DATA_INDEX
Expand Down Expand Up @@ -5085,20 +5068,18 @@ module Crystal

# Looks ahead next tokens to check whether they indicate type.
def type_start?(*, consume_newlines)
old_pos, old_line, old_column = current_pos, @line_number, @column_number
@temp_token.copy_from(@token)
peek_ahead do
begin
if consume_newlines
next_token_skip_space_or_newline
else
next_token_skip_space
end

begin
if consume_newlines
next_token_skip_space_or_newline
else
next_token_skip_space
type_start?
rescue
false
end

type_start?
ensure
@token.copy_from(@temp_token)
self.current_pos, @line_number, @column_number = old_pos, old_line, old_column
end
end

Expand Down
31 changes: 30 additions & 1 deletion src/compiler/crystal/tools/formatter.cr
Original file line number Diff line number Diff line change
Expand Up @@ -3568,7 +3568,16 @@ module Crystal
write " " unless has_parentheses
skip_space

if exp.is_a?(TupleLiteral) && @token.type != :"{"
# If the number of consecutive `{`s starting a tuple literal is 1 less
# than the level of tuple nesting in the actual AST node, this means the
# parser synthesized a TupleLiteral from multiple expressions, e.g.
#
# return {1, 2}, 3, 4
# return { {1, 2}, 3, 4 }
#
# The tuple depth is 2 in both cases but only 1 leading curly brace is
# present on the first return.
if exp.is_a?(TupleLiteral) && opening_curly_brace_count < leading_tuple_depth(exp)
format_args(exp.elements, has_parentheses)
skip_space if has_parentheses
else
Expand All @@ -3582,6 +3591,26 @@ module Crystal
false
end

def opening_curly_brace_count
@lexer.peek_ahead do
count = 0
while @lexer.token.type == :"{"
count += 1
@lexer.next_token_skip_space_or_newline
end
count
end
end

def leading_tuple_depth(exp)
count = 0
while exp.is_a?(TupleLiteral)
count += 1
exp = exp.elements.first?
end
count
end

def visit(node : Yield)
if scope = node.scope
write_keyword :with, " "
Expand Down