crystal-lang / crystal

The Crystal Programming Language
https://crystal-lang.org
Apache License 2.0
19.45k stars 1.62k forks source link

Formatter error when assigning to nilable type in property missing space after name #14482

Open Blacksmoke16 opened 6 months ago

Blacksmoke16 commented 6 months ago
class Foo
  property id: Int32? = 1
end

Trying to format this results in:

expecting =, not `IDENT, id`, at :2:12 (Exception)
  from /crystal/src/compiler/crystal/tools/formatter.cr:5170:9 in '??'
  from /crystal/src/compiler/crystal/tools/formatter.cr:3395:7 in 'visit'
  from /crystal/src/compiler/crystal/syntax/visitor.cr:27:12 in 'accept'
  from /crystal/src/compiler/crystal/tools/formatter.cr:4815:7 in '??'
  from /crystal/src/compiler/crystal/tools/formatter.cr:4664:7 in 'format_nested_with_end'
  from /crystal/src/compiler/crystal/syntax/visitor.cr:27:12 in 'accept'
  from /crystal/src/compiler/crystal/syntax/visitor.cr:20:7 in 'visit'
  from /crystal/src/compiler/crystal/syntax/visitor.cr:27:12 in 'accept'
  from /crystal/src/compiler/crystal/tools/formatter.cr:23:7 in 'format_source'
  from /crystal/src/compiler/crystal/command/format.cr:133:7 in 'format_many'
  from /crystal/src/compiler/crystal/command/format.cr:63:5 in 'format'
  from /crystal/src/compiler/crystal/command.cr:179:7 in 'tool'
  from /crystal/src/compiler/crystal/command.cr:117:7 in 'run'
  from /crystal/src/compiler/crystal.cr:11:1 in '__crystal_main'
  from /crystal/src/crystal/main.cr:129:5 in 'main'
  from src/env/__libc_start_main.c:95:2 in 'libc_start_main_stage2'

Seems it does not like the invalid syntax in combination with an assignment to a nilable type.

Also somewhat related to https://github.com/crystal-lang/crystal/issues/10698.

HertzDevil commented 6 months ago

It looks like this snippet straight up parses to something without the id at all:

macro f(x)
  {% p x %} # => class Foo; property = 1; end
end

f(class Foo
    property id: Int32? = 1
  end)
HertzDevil commented 6 months ago

So it turns out the parser is very lax when detecting valid assignment targets. In particular, Crystal::Parser#can_be_assigned? accepts far more Call nodes than #multi_assign_target?, so all the following lines have a similar issue:

foo() = 1         # => foo = 1
foo(x: 1) = 2     # => foo = 2
foo.[](1) = 2     # => foo[1] = 2
foo.[](x: 1) = 2  # => foo[x: 1] = 2
foo.[](&.bar) = 2 # => foo.[]=(2, &.bar)

foo x: 1 = 2 # => foo = 2

# these type names are also valid regular code
foo x: T? = 1     # => foo = 1
foo x: {T} = 1    # => foo = 1
foo x: {y: T} = 1 # => foo = 1

But note:

foo x: T = 1    # => foo(x: T = 1)
foo x: T[1] = 1 # => foo(x: T[1] = 1)
foo x: T* = 1   # Error: unexpected token: "="

The lines that involve parentheses should definitely be syntax errors.