Skip to content

Column positions in diagnostics are off by one #13184

@lukaszsamson

Description

@lukaszsamson

Elixir and Erlang/OTP versions

Erlang/OTP 26 [erts-14.1.1] [source] [64-bit] [smp:12:12] [ds:12:12:10] [async-threads:1] [jit]

Elixir 1.16.0-rc.1 (81a12b7) (compiled with Erlang/OTP 26)

Operating system

macOS

Current behavior

The documentation and typespec for https://hexdocs.pm/mix/1.15.7/Mix.Task.Compiler.Diagnostic.html#t:position/0 states that line is 1-based and column is 0-based
In case of https://hexdocs.pm/elixir/Code.html#t:position/0 it's not documented but the typespec is exactly the same.

However, when I try it, the APIs return 1-based columns for both console prints and diagnostics

Here I expect the position to column to be {1, 0} (though we can argue wether the error actually is on 0 or 1 column)

%Mix.Task.Compiler.Diagnostic{
  file: "lib/a.ex",
  severity: :error,
  message: "** (TokenMissingError) token missing on lib/a.ex:1:2:\n    error: missing terminator: ]\n    │\n  1 │ [\n    │ │└ missing closing delimiter (expected \"]\")\n    │ └ unclosed delimiter\n    │\n    └─ lib/a.ex:1:2\n    (elixir 1.16.0-rc.1) lib/kernel/parallel_compiler.ex:428: anonymous fn/5 in Kernel.ParallelCompiler.spawn_workers/8\n",
  position: {1, 1},
  compiler_name: "Elixir",
  span: nil,
  details: %TokenMissingError{
    file: "lib/a.ex",
    line: 1,
    column: 1,
    end_line: 2,
    line_offset: 0,
    snippet: "[\n",
    opening_delimiter: :"[",
    description: "missing terminator: ]"
  },
  stacktrace: [
    {:elixir_compiler, :string, 3, [file: ~c"src/elixir_compiler.erl", line: 7]},
    {Kernel.ParallelCompiler, :"-spawn_workers/8-fun-0-", 5,
     [file: ~c"lib/kernel/parallel_compiler.ex", line: 428]}
  ]
}
iex(3)> try do
...(3)> Code.with_diagnostics([log: true], fn -> Code.compile_string("[") end)
...(3)> rescue
...(3)> e -> e
...(3)> end

** (TokenMissingError) token missing on nofile:1:2:
    error: missing terminator: ]
    │
  1 │ [
    │ │└ missing closing delimiter (expected "]")
    │ └ unclosed delimiter
    │
    └─ nofile:1:2
    (elixir 1.16.0-rc.1) src/elixir_compiler.erl:7: :elixir_compiler.string/3
    iex:3: (file)
    iex:3: (file)

%TokenMissingError{
  file: "nofile",
  line: 1,
  column: 1,
  end_line: 1,
  line_offset: 0,
  snippet: "[",
  opening_delimiter: :"[",
  description: "missing terminator: ]"
}

Here the situation is clear - I expect {1, 0} and based on typespec from https://hexdocs.pm/elixir/main/Code.html#t:diagnostic/1 I expect span to be {1, 3}

iex(2)> Code.with_diagnostics([log: true], fn -> Code.compile_string("asd = 0") end)
warning: variable "asd" is unused (if the variable is not meant to be used, prefix it with an underscore)
└─ nofile:1:1

{[],
 [
   %{
     message: "variable \"asd\" is unused (if the variable is not meant to be used, prefix it with an underscore)",
     position: {1, 1},
     file: "nofile",
     stacktrace: [],
     span: {1, 4},
     severity: :warning
   }
 ]}

Expected behavior

All this is really inconsistent and confusing. Some APIs (e.g. Code.Fragment) use 1-based columns, some use 0-based, some does not work as documented. I'm not sure what's the way forwards.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions