Skip to content

Commit

Permalink
Fix formatting generic types with suffix (crystal-lang#11187)
Browse files Browse the repository at this point in the history
  • Loading branch information
makenowjust authored Sep 29, 2021
1 parent c172bd9 commit 6977731
Show file tree
Hide file tree
Showing 5 changed files with 43 additions and 17 deletions.
6 changes: 6 additions & 0 deletions spec/compiler/formatter/formatter_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -1111,6 +1111,12 @@ describe Crystal::Formatter do
assert_format "foo : (F(A)) | D"
assert_format "foo : ( A | B )", "foo : (A | B)"

# #11179
assert_format "foo : Pointer(Foo)*"
assert_format "foo : Foo*****"
assert_format "foo : Foo * * * * *", "foo : Foo*****"
assert_format "foo : StaticArray(Foo, 12)[34]"

assert_format "def foo(x : (A | B)) \n end", "def foo(x : (A | B))\nend"
assert_format "foo : (String -> String?) | (String)"
assert_format "foo : (Array(String)?) | String"
Expand Down
4 changes: 4 additions & 0 deletions spec/compiler/parser/parser_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -1233,6 +1233,10 @@ module Crystal
it_parses "@a : Foo = 1", TypeDeclaration.new("@a".instance_var, "Foo".path, 1.int32)
it_parses "@@a : Foo = 1", TypeDeclaration.new("@@a".class_var, "Foo".path, 1.int32)

it_parses "Foo?", Generic.new("Union".path(global: true), ["Foo".path, "Nil".path(global: true)] of ASTNode)
it_parses "a : Foo*", TypeDeclaration.new("a".var, Generic.new("Pointer".path(global: true), ["Foo".path] of ASTNode, suffix: Generic::Suffix::Asterisk))
it_parses "a : Foo[12]", TypeDeclaration.new("a".var, Generic.new("StaticArray".path(global: true), ["Foo".path, 12.int32] of ASTNode, suffix: Generic::Suffix::Bracket))

it_parses "a = uninitialized Foo; a", [UninitializedVar.new("a".var, "Foo".path), "a".var]
it_parses "@a = uninitialized Foo", UninitializedVar.new("@a".instance_var, "Foo".path)
it_parses "@@a = uninitialized Foo", UninitializedVar.new("@@a".class_var, "Foo".path)
Expand Down
15 changes: 10 additions & 5 deletions src/compiler/crystal/syntax/ast.cr
Original file line number Diff line number Diff line change
Expand Up @@ -1481,10 +1481,16 @@ module Crystal
property type_vars : Array(ASTNode)
property named_args : Array(NamedArgument)?

# `true` if this Generic was parsed from `T?`
property? question = false
property suffix : Suffix

def initialize(@name, @type_vars : Array, @named_args = nil)
enum Suffix
None
Question # T?
Asterisk # T*
Bracket # T[N]
end

def initialize(@name, @type_vars : Array, @named_args = nil, @suffix = Suffix::None)
end

def self.new(name, type_var : ASTNode)
Expand All @@ -1498,8 +1504,7 @@ module Crystal
end

def clone_without_location
generic = Generic.new(@name.clone, @type_vars.clone, @named_args.clone)
generic.question = question?
generic = Generic.new(@name.clone, @type_vars.clone, @named_args.clone, @suffix)
generic
end

Expand Down
10 changes: 7 additions & 3 deletions src/compiler/crystal/syntax/parser.cr
Original file line number Diff line number Diff line change
Expand Up @@ -5047,16 +5047,20 @@ module Crystal

def make_nilable_expression(type)
type = Generic.new(Path.global("Union").at(type), [type, Path.global("Nil").at(type)]).at(type)
type.question = true
type.suffix = :question
type
end

def make_pointer_type(type)
Generic.new(Path.global("Pointer").at(type), [type] of ASTNode).at(type)
type = Generic.new(Path.global("Pointer").at(type), [type] of ASTNode).at(type)
type.suffix = :asterisk
type
end

def make_static_array_type(type, size)
Generic.new(Path.global("StaticArray").at(type), [type, size] of ASTNode).at(type)
type = Generic.new(Path.global("StaticArray").at(type), [type, size] of ASTNode).at(type)
type.suffix = :bracket
type
end

def make_tuple_type(types)
Expand Down
25 changes: 16 additions & 9 deletions src/compiler/crystal/tools/formatter.cr
Original file line number Diff line number Diff line change
Expand Up @@ -1107,36 +1107,43 @@ module Crystal
next_token_skip_space_or_newline
end

if node.question?
if node.suffix.question?
node.type_vars[0].accept self
skip_space
write_token :"?"
return false
end

# Check if it's T* instead of Pointer(T)
if first_name == "Pointer" && @token.value != "Pointer"
if node.suffix.asterisk?
# Count nesting asterisk
asterisks = 1
while (type_var = node.type_vars.first) && type_var.is_a?(Generic) && type_var.suffix.asterisk?
node = type_var
asterisks += 1
end

type_var = node.type_vars.first
accept type_var
skip_space_or_newline

# Another case is T** instead of Pointer(Pointer(T))
if @token.type == :"**"
if type_var.is_a?(Generic)
while asterisks > 0
skip_space
if @token.type == :"**" && asterisks >= 2
write "**"
next_token
asterisks -= 2
else
# Skip
write_token :"*"
asterisks -= 1
end
else
write_token :"*"
end

return false
end

# Check if it's T[N] instead of StaticArray(T, N)
if first_name == "StaticArray" && @token.value != "StaticArray"
if node.suffix.bracket?
accept node.type_vars[0]
skip_space_or_newline
write_token :"["
Expand Down

0 comments on commit 6977731

Please sign in to comment.