diff --git a/spec/compiler/formatter/formatter_spec.cr b/spec/compiler/formatter/formatter_spec.cr index 35d921def563..fcfb9171b8d5 100644 --- a/spec/compiler/formatter/formatter_spec.cr +++ b/spec/compiler/formatter/formatter_spec.cr @@ -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" diff --git a/spec/compiler/parser/parser_spec.cr b/spec/compiler/parser/parser_spec.cr index b33731425dbb..cb61eb6b907d 100644 --- a/spec/compiler/parser/parser_spec.cr +++ b/spec/compiler/parser/parser_spec.cr @@ -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) diff --git a/src/compiler/crystal/syntax/ast.cr b/src/compiler/crystal/syntax/ast.cr index a52eebe6da9b..faf6ec9c34c0 100644 --- a/src/compiler/crystal/syntax/ast.cr +++ b/src/compiler/crystal/syntax/ast.cr @@ -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) @@ -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 diff --git a/src/compiler/crystal/syntax/parser.cr b/src/compiler/crystal/syntax/parser.cr index 366e7c83101a..2694c36d4a1b 100644 --- a/src/compiler/crystal/syntax/parser.cr +++ b/src/compiler/crystal/syntax/parser.cr @@ -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) diff --git a/src/compiler/crystal/tools/formatter.cr b/src/compiler/crystal/tools/formatter.cr index efc9951b974d..f3ada2568434 100644 --- a/src/compiler/crystal/tools/formatter.cr +++ b/src/compiler/crystal/tools/formatter.cr @@ -1107,7 +1107,7 @@ 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 :"?" @@ -1115,28 +1115,35 @@ module Crystal 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 :"["