Skip to content

bug: anonymous generic call signatures fail to parse when separated only by line breaks #335

@sachiniyer

Description

@sachiniyer

Did you check existing issues?

  • I have read all the tree-sitter docs if it relates to using the parser
  • I have searched the existing issues of tree-sitter-typescript

Tree-Sitter CLI Version, if relevant (output of tree-sitter --version)

tree-sitter 0.25.6 (bf655c0beaf4943573543fa77c58e8006ff34971)

Describe the bug

Main Issue

Tree-sitter TypeScript parser fails to parse multiple anonymous generic call signatures (e.g., <T>(value: T): T) when they are separated only by line breaks without semicolons.

Compilation in typescriptlang.org/play

Input code:

interface TestInterface {
    <T>(value: T): T
    <T>(value: T, other: any): T
}

.js (empty, which makes sense)

"use strict";

.d.ts

interface TestInterface {
    <T>(value: T): T;
    <T>(value: T, other: any): T;
}

Playground Link

Test Suite

Here is a test suite I wrote when narrowing the bug

import TreeSitter from "tree-sitter";
import pkg from "tree-sitter-typescript";
const { typescript } = pkg;

describe("Treesitter parsing error repro", () => {
  const testDirectParsing = (code: string): boolean => {
    const parser = new TreeSitter();
    parser.setLanguage(typescript);
    const tree = parser.parse(code);
    const rootNode = tree.rootNode;
    return !rootNode.hasError;
  };

  test("Interface with two generic method overloads fails", () => {
    const failingInterface = `interface TestInterface {
  <T>(value: T): T
  <T>(value: T, other: any): T
}`;
    const result = testDirectParsing(failingInterface);
    expect(result).toBe(false);
  });

  test("Interface with three generic method overloads fails", () => {
    const failingInterface = `interface TestInterface {
  <T>(value: T): T
  <T>(value: T, other: any): T
  <T>(value: T, other: any, last: string): T
}`;
    const result = testDirectParsing(failingInterface);
    expect(result).toBe(false);
  });

  test("Interface with two constructor overloads succeeds", () => {
    const failingInterface = `interface TestInterface {
  new <T>(value: T): T
  new <T>(value: T, other: any): T
}`;
    const result = testDirectParsing(failingInterface);
    expect(result).toBe(true);
  });

  test("Interface index signature overloads succeeds", () => {
    const failingInterface = `interface TestInterface {
  <T>(value: T): T
  [key: string]: any
}`;
    const result = testDirectParsing(failingInterface);
    expect(result).toBe(true);
  });

  test("Interface mixed signature types succeeds", () => {
    const failingInterface = `interface TestInterface {
  <T>(value: T): T
  process<T>(value: T): T
}`;
    const result = testDirectParsing(failingInterface);
    expect(result).toBe(true);
  });

  test("Interface with two generic method overloads and method signatures succeeds", () => {
    const failingInterface = `interface TestInterface {
  process<T>(value: T): T
  process<T>(value: T, other: any): T
}`;
    const result = testDirectParsing(failingInterface);
    expect(result).toBe(true);
  });

  test("Interface with two non-generic method overloads succeeds", () => {
    const failingInterface = `interface TestInterface {
  method(a: string): string
  method(a: number): number
}`;
    const result = testDirectParsing(failingInterface);
    expect(result).toBe(true);
  });

  test("Interface with two generic method overloads and semicolon separation succeeds", () => {
    const failingInterface = `interface TestInterface {
  <T>(value: T): T;
  <T>(value: T, other: any): T;
}`;
    const result = testDirectParsing(failingInterface);
    expect(result).toBe(true);
  });

  test("Interface with two generic method overloads and single semicolon separation succeeds", () => {
    const failingInterface = `interface TestInterface {
  <T>(value: T): T;
  <T>(value: T, other: any): T
}`;
    const result = testDirectParsing(failingInterface);
    expect(result).toBe(true);
  });

  test("Interface with two simple attributes succeeds", () => {
    const failingInterface = `interface TestInterface {
  prop1: string
  prop2: string
}`;
    const result = testDirectParsing(failingInterface);
    expect(result).toBe(true);
  });

  test("Single generic method works", () => {
    const workingInterface = `interface TestInterface {
  <T>(value: T): T
}`;

    const result = testDirectParsing(workingInterface);

    expect(result).toBe(true);
  });

  test("Single generic method works with 2 parameters", () => {
    const workingInterface = `interface TestInterface {
  <T>(value: T, other: any): T
}`;

    const result = testDirectParsing(workingInterface);

    expect(result).toBe(true);
  });
});

Steps To Reproduce/Bad Parse Tree

(program [0, 0] - [4, 0]
  (interface_declaration [0, 0] - [2, 4]
    name: (type_identifier [0, 10] - [0, 23])
    body: (interface_body [0, 24] - [2, 4]
      (call_signature [1, 1] - [2, 4]
        type_parameters: (type_parameters [1, 1] - [1, 4]
          (type_parameter [1, 2] - [1, 3]
            name: (type_identifier [1, 2] - [1, 3])))
        parameters: (formal_parameters [1, 4] - [1, 14]
          (required_parameter [1, 5] - [1, 13]
            pattern: (identifier [1, 5] - [1, 10])
            type: (type_annotation [1, 10] - [1, 13]
              (type_identifier [1, 12] - [1, 13]))))
        return_type: (type_annotation [1, 14] - [2, 4]
          (generic_type [1, 16] - [2, 4]
            name: (type_identifier [1, 16] - [1, 17])
            type_arguments: (type_arguments [2, 1] - [2, 4]
              (type_identifier [2, 2] - [2, 3])))))))
  (ERROR [2, 4] - [3, 1]
    parameters: (formal_parameters [2, 4] - [2, 26]
      (required_parameter [2, 5] - [2, 13]
        pattern: (identifier [2, 5] - [2, 10])
        type: (type_annotation [2, 10] - [2, 13]
          (type_identifier [2, 12] - [2, 13])))
      (required_parameter [2, 15] - [2, 25]
        pattern: (identifier [2, 15] - [2, 20])
        type: (type_annotation [2, 20] - [2, 25]
          (predefined_type [2, 22] - [2, 25]))))
    return_type: (type_annotation [2, 26] - [2, 29]
      (type_identifier [2, 28] - [2, 29]))))
test_interface.ts	Parse:    0.42 ms	   178 bytes/ms	(MISSING "}" [2, 4] - [2, 4])

Expected Behavior/Parse Tree

(interface_body [0, 24] - [3, 1]
  (call_signature [1, 1] - [1, 17]  // First signature
    type_parameters: (type_parameters [1, 1] - [1, 4] ...)
    parameters: (formal_parameters [1, 4] - [1, 14] ...)
    return_type: (type_annotation [1, 14] - [1, 17] ...))
  (call_signature [2, 1] - [2, 29]  // Second signature
    type_parameters: (type_parameters [2, 1] - [2, 4] ...)
    parameters: (formal_parameters [2, 4] - [2, 26] ...)
    return_type: (type_annotation [2, 26] - [2, 29] ...)))

Repro

interface TestInterface {
    <T>(value: T): T
    <T>(value: T, other: any): T
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions