Repository: python/mypy
Pull request: report columns in 1-based, not 0-based
fixes #4475

Problem statement:
Are column numbers base-0 or base-1?
IIUC Column numbers (as shown by `--show-column-numbers` are 0-based (as opposed to line numbers, which are 1-based). But in Emacs `*compilation*` mode, it seems column numbers (if present) are interpreted as 1-based, so the cursor is placed one character to the left of the actual error.

Do we know what other tools that understand/use column numbers do?

Hints from issue discussion:
Vim also has 1-based column numbering (at least this is what I always see by default on different machines where I work). The same is true for PyCharm. In general my impression is that 1-based column numeration is much more popular among text editors.
Let's fix it.
I would like to take this if you don’t mind?
Go for it!

Commit message: report columns as 1 based

Patch to review:
diff --git a/mypy/errors.py b/mypy/errors.py
index 4cc7f077ea56..9f2b1a091261 100644
--- a/mypy/errors.py
+++ b/mypy/errors.py
@@ -411,7 +411,8 @@ def format_messages(self, error_info: List[ErrorInfo]) -> List[str]:
             if file is not None:
                 if self.show_column_numbers and line is not None and line >= 0 \
                         and column is not None and column >= 0:
-                    srcloc = '{}:{}:{}'.format(file, line, column)
+                    col = 1 + column
+                    srcloc = '{}:{}:{}'.format(file, line, col)
                 elif line is not None and line >= 0:
                     srcloc = '{}:{}'.format(file, line)
                 else:
diff --git a/test-data/unit/check-columns.test b/test-data/unit/check-columns.test
index 9b2c0b9f9f71..44d9d6881a2e 100644
--- a/test-data/unit/check-columns.test
+++ b/test-data/unit/check-columns.test
@@ -2,7 +2,7 @@
 # flags: --show-column-numbers
 1 +
 [out]
-main:2:4: error: invalid syntax
+main:2:5: error: invalid syntax
 
 
 [case testColumnsNestedFunctions]
@@ -15,8 +15,8 @@ def f() -> 'A':
 class A: pass
 class B: pass
 [out]
-main:5:8: error: Incompatible return value type (got "A", expected "B")
-main:6:4: error: Incompatible return value type (got "B", expected "A")
+main:5:9: error: Incompatible return value type (got "A", expected "B")
+main:6:5: error: Incompatible return value type (got "B", expected "A")
 
 [case testColumnsNestedFunctionsWithFastParse]
 # flags: --show-column-numbers
@@ -28,8 +28,8 @@ def f() -> 'A':
 class A: pass
 class B: pass
 [out]
-main:5:8: error: Incompatible return value type (got "A", expected "B")
-main:6:4: error: Incompatible return value type (got "B", expected "A")
+main:5:9: error: Incompatible return value type (got "A", expected "B")
+main:6:5: error: Incompatible return value type (got "B", expected "A")
 
 
 [case testColumnsMethodDefaultArgumentsAndSignatureAsComment]
@@ -40,28 +40,28 @@ class A:
         pass
 A().f()
 A().f(1)
-A().f('') # E:0: Argument 1 to "f" of "A" has incompatible type "str"; expected "int"
-A().f(1, 1) # E:0: Argument 2 to "f" of "A" has incompatible type "int"; expected "str"
-A().f(1, 'hello', 'hi') # E:0: Too many arguments for "f" of "A"
+A().f('') # E:1: Argument 1 to "f" of "A" has incompatible type "str"; expected "int"
+A().f(1, 1) # E:1: Argument 2 to "f" of "A" has incompatible type "int"; expected "str"
+A().f(1, 'hello', 'hi') # E:1: Too many arguments for "f" of "A"
 
 [case testColumnsMultipleStatementsPerLine]
 # flags: --show-column-numbers
-x = 1
+x = 15
 y = 'hello'
 x = 2; y = x; y += 1
 [out]
-main:4:7: error: Incompatible types in assignment (expression has type "int", variable has type "str")
-main:4:14: error: Unsupported operand types for + ("str" and "int")
+main:4:8: error: Incompatible types in assignment (expression has type "int", variable has type "str")
+main:4:15: error: Unsupported operand types for + ("str" and "int")
 
 [case testColumnsSimpleIsinstance]
 # flags: --show-column-numbers
 import typing
 def f(x: object, n: int, s: str) -> None:
-    n = x # E:4: Incompatible types in assignment (expression has type "object", variable has type "int")
+    n = x # E:5: Incompatible types in assignment (expression has type "object", variable has type "int")
     if isinstance(x, int):
         n = x
-        s = x # E:8: Incompatible types in assignment (expression has type "int", variable has type "str")
-    n = x # E:4: Incompatible types in assignment (expression has type "object", variable has type "int")
+        s = x # E:9: Incompatible types in assignment (expression has type "int", variable has type "str")
+    n = x # E:5: Incompatible types in assignment (expression has type "object", variable has type "int")
 [builtins fixtures/isinstance.pyi]
 [out]
 
diff --git a/test-data/unit/check-generics.test b/test-data/unit/check-generics.test
index 8b9b4d52afc9..6e69b726216e 100644
--- a/test-data/unit/check-generics.test
+++ b/test-data/unit/check-generics.test
@@ -615,17 +615,17 @@ X = T # Error
 
 [builtins fixtures/list.pyi]
 [out]
-main:9:4: error: "Node" expects 2 type arguments, but 1 given
-main:11:4: error: "Node" expects 2 type arguments, but 3 given
-main:15:9: error: "list" expects 1 type argument, but 2 given
-main:16:18: error: "list" expects 1 type argument, but 2 given
-main:17:24: error: "Node" expects 2 type arguments, but 1 given
-main:18:3: error: "Node" expects 2 type arguments, but 1 given
-main:19:4: error: Bad number of arguments for type alias, expected: 1, given: 2
-main:19:4: error: "Node" expects 2 type arguments, but 1 given
-main:22:0: error: Revealed type is '__main__.Node[builtins.int, builtins.str]'
-main:24:0: error: Revealed type is '__main__.Node[__main__.Node[builtins.int, builtins.int], builtins.list[builtins.int]]'
-main:26:4: error: Type variable "__main__.T" is invalid as target for type alias
+main:9:5: error: "Node" expects 2 type arguments, but 1 given
+main:11:5: error: "Node" expects 2 type arguments, but 3 given
+main:15:10: error: "list" expects 1 type argument, but 2 given
+main:16:19: error: "list" expects 1 type argument, but 2 given
+main:17:25: error: "Node" expects 2 type arguments, but 1 given
+main:18:4: error: "Node" expects 2 type arguments, but 1 given
+main:19:5: error: Bad number of arguments for type alias, expected: 1, given: 2
+main:19:5: error: "Node" expects 2 type arguments, but 1 given
+main:22:1: error: Revealed type is '__main__.Node[builtins.int, builtins.str]'
+main:24:1: error: Revealed type is '__main__.Node[__main__.Node[builtins.int, builtins.int], builtins.list[builtins.int]]'
+main:26:5: error: Type variable "__main__.T" is invalid as target for type alias
 
 [case testGenericTypeAliasesForAliases]
 from typing import TypeVar, Generic, List, Union
@@ -1068,7 +1068,7 @@ y = None # type: SameA[str] # Two errors here, for both args of A
 
 [builtins fixtures/list.pyi]
 [out]
-main:9:7: error: Value of type variable "T" of "A" cannot be "str"
+main:9:8: error: Value of type variable "T" of "A" cannot be "str"
 main:13: error: Value of type variable "T" of "A" cannot be "str"
 main:13: error: Value of type variable "S" of "A" cannot be "str"
 


Code review comments:
diff_hunk: @@ -411,7 +411,8 @@ def format_messages(self, error_info: List[ErrorInfo]) -> List[str]:
             if file is not None:
                 if self.show_column_numbers and line is not None and line >= 0 \
                         and column is not None and column >= 0:
-                    srcloc = '{}:{}:{}'.format(file, line, column)
+                    col = 1 + column
+                    srcloc = '{}:{}:{}'.format(file, line, col)
path: mypy/errors.py
text: @user1:
Any reason why you're not just inlining `column + 1` here?