opentau

App Listed Dormant

Using Large Language Models for Repo-wide Type Prediction

Checked · Sources

Downloads
Not measured
Dependents
Not applicable
Stars
121
Forks
7
Contributors
2
Commits
513

About

From the project README ·

OpenTau: Using Large Language Models for Gradual Type Inference

Implementation for the paper: Type Prediction With Program Decomposition and Fill-in-the-Type Training. Federico Cassano, Ming-Ho Yee, Noah Shinn, Arjun Guha, Steven Holtzen.

Type inference for gradually-typed languages such as TypeScript and Python has become increasingly prevalent in the field of programming languages. However, current approaches often struggle with inferring descriptive types in cases in which user-defined type annotations are absent, especially when inferring function signatures.

Read the full README

This has motivated automated type prediction: given an untyped program, produce a well-typed output program. Large language models (LLMs) are promising for type prediction, but there are challenges: fill-in-the-middle performs poorly, programs may not fit into the context window, generated types may not type check, and it is difficult to measure how well-typed the output program is. We address these challenges by building OpenTau, a search-based approach for type prediction that leverages large language models. We propose a new metric for type prediction quality, give a tree-based program decomposition that searches a space of generated types, and present fill-in-the-type fine-tuning for LLMs. We evaluate our work with a new dataset for TypeScript type prediction, and show that 47.4% of files type check (14.5% absolute improvement) with an overall rate of 3.3 type errors per file.

Additionally, we build two protocols for implementing additional languages and models. In our work, we implement a TypeScript compiler that respects the protocol and a SantaCoder server that respects the other protocol. An optional OpenAI model endpoint also implements the protocol, but it is unmaintained and not recommended for use. Implementing the respective protocols is relatively straightforward. More information can be found in our class final project submission (as this work started as a class project for CS 4100 at Northeastern University).

Cite

@misc{cassano2023type,
      title={Type Prediction With Program Decomposition and Fill-in-the-Type Training}, 
      author={Federico Cassano and Ming-Ho Yee and Noah Shinn and Arjun Guha and Steven Holtzen},
      year={2023},
      eprint={2305.17145},
      archivePrefix={arXiv},
      primaryClass={cs.SE}
}

Usage

We have implemented an OpenTau in Rust, which can be utilized in three ways:

  1. As a simple CLI client that will type-infer a given program. (more info in ./client)
  2. As a library, that exposes numerous abstractions for interacting with different compilers, models, and type prediction strategies. (more info in ./client)
  3. As an evaluation tool, to analyze the performance of the combinations of different models, languages, datasets, and type prediction strategies on the task of type prediction. (more info in ./evaluator)

We are in the review process for our paper: Type Prediction With Program Decomposition and Fill-in-the-Type Training. Federico Cassano, Ming-Ho Yee, Noah Shinn, Arjun Guha, Steven Holtzen.

Requirements

  • rust
  • Incoder/SantaCoder model requirements:
    • torch
    • tokenizers>=0.12
    • transformers
  • TypeScript compiler requirements:
    • ts-node
    • tsc
  • Python compiler requirements (Work in progress):
    • mypy | pyright for static type checking
    • redbaron for AST parsing with comments
  • pandoc ONLY for building the report

Installation

Run make while being in the directory

The output binary (symlinked) will be at /out/client

Example completion

Our system was able to type-infer this program:

const findAllPeople = function (n, meetings, firstPerson) {
  meetings.sort((a, b) => a[2] - b[2]);
  const uf = new UnionFind(n);
  uf.connect(0, firstPerson);
  let ppl = [];
  for (let i = 0, len = meetings.length; i < len; ) {
    ppl = [];
    let time = meetings[i][2];
    while (i < len && meetings[i][2] === time) {
      uf.connect(meetings[i][0], meetings[i][1]);
      ppl.push(meetings[i][0]);
      ppl.push(meetings[i][1]);
      i++;
    }
    for (let n of ppl) {
      if (!uf.connected(0, n)) uf.reset(n);
    }
  }
  let ans = [];
  for (let i = 0; i < n; ++i) {
    if (uf.connected(0, i)) ans.push(i);
  }
  return ans;
};

class UnionFind {
  arr;

  constructor(n) {
    this.arr = Array(n).fill(null);
    this.arr.forEach((e, i, arr) => (arr[i] = i));
  }
  connect(a, b) {
    this.arr[this.find(a)] = this.find(this.arr[b]);
  }
  find(a) {
    return this.arr[a] === a ? a : (this.arr[a] = this.find(this.arr[a]));
  }
  connected(a, b) {
    return this.find(a) === this.find(b);
  }
  reset(a) {
    this.arr[a] = a;
  }
}

And annotate it with these types:

const findAllPeople: (
  n: number,
  meetings: number[][],
  firstPerson: number
) => number[] = function (n, meetings, firstPerson) {
  meetings.sort((a, b) => a[2] - b[2]);
  const uf: UnionFind = new UnionFind(n);
  uf.connect(0, firstPerson);
  let ppl: number[] = [];
  for (let i = 0, len = meetings.length; i < len; ) {
    ppl = [];
    let time: number = meetings[i][2];
    while (i < len && meetings[i][2] === time) {
      uf.connect(meetings[i][0], meetings[i][1]);
      ppl.push(meetings[i][0]);
      ppl.push(meetings[i][1]);
      i++;
    }
    for (let n of ppl) {
      if (!uf.connected(0, n)) uf.reset(n);
    }
  }
  let ans: number[] = [];
  for (let i = 0; i < n; ++i) {
    if (uf.connected(0, i)) ans.push(i);
  }
  return ans;
};

class UnionFind {
  arr: number[];
  constructor(n) {
    this.arr = Array(n).fill(null);
    this.arr.forEach((e, i, arr) => (arr[i] = i));
  }
  connect(a: number, b: number): void {
    this.arr[this.find(a)] = this.find(this.arr[b]);
  }
  find(a: number): number {
    return this.arr[a] === a ? a : (this.arr[a] = this.find(this.arr[a]));
  }
  connected(a: number, b: number): boolean {
    return this.find(a) === this.find(b);
  }
  reset(a: number): void {
    this.arr[a] = a;
  }
}

While TypeScript's type inference only managed to infer these types (too many anys and loose typing):

const findAllPeople = function (n: number, meetings: any[], firstPerson: any) {
  meetings.sort((a: number[], b: number[]) => a[2] - b[2]);
  const uf = new UnionFind(n);
  uf.connect(0, firstPerson);
  let ppl = [];
  for (let i = 0, len = meetings.length; i < len; ) {
    ppl = [];
    let time = meetings[i][2];
    while (i < len && meetings[i][2] === time) {
      uf.connect(meetings[i][0], meetings[i][1]);
      ppl.push(meetings[i][0]);
      ppl.push(meetings[i][1]);
      i++;
    }
    for (let n of ppl) {
      if (!uf.connected(0, n)) uf.reset(n);
    }
  }
  let ans = [];
  for (let i = 0; i < n; ++i) {
    if (uf.connected(0, i)) ans.push(i);
  }
  return ans;
};

class UnionFind {
  arr: any[];

  constructor(n: any) {
    this.arr = Array(n).fill(null);
    this.arr.forEach(
      (e: any, i: string | number, arr: { [x: string]: any }) => (arr[i] = i)
    );
  }
  connect(a: number, b: string | number) {
    this.arr[this.find(a)] = this.find(this.arr[b]);
  }
  find(a: string | number) {
    return this.arr[a] === a ? a : (this.arr[a] = this.find(this.arr[a]));
  }
  connected(a: number, b: number) {
    return this.find(a) === this.find(b);
  }
  reset(a: string | number) {
    this.arr[a] = a;
  }
}

Note that TypeScript's inference type annotated non let-bound arrow functions, while our system didn't. We believe that these functions should be left untyped, as the signature of the function that calls them should be typed, and TypeScript's type-inference should enforce those rules. Our system will not battle with TypeScript's type-inference, it will try to work alongside it. Additionally, our system will not perform any type-migrations, i.e. it will not change already defined types. This is to further enforce the coalition between our system and TypeScript's.

Another Example: Generics Inference

Our system is able to fill out generic types.

var sumFourDivisors = function (nums) {
  let res = 0;

  for (const e of nums) {
    const set = helper(e);
    if (set.size === 4) {
      for (const i of set) res += i;
    }
  }

  return res;

  function helper(num) {
    const set = new Set();
    const r = ~~(Math.sqrt(num) + 1);
    for (let i = 1; i < r; i++) {
      if (num % i === 0) {
        set.add(i);
        set.add(num / i);
      }
    }
    return set;
  }
};

to

var sumFourDivisors: (nums: number[]) => number = function (nums) {
  let res: number = 0;
  for (const e of nums) {
    const set: Set<number> = helper(e);
    if (set.size === 4) {
      for (const i of set) res += i;
    }
  }
  return res;
  function helper(num: number): Set<number> {
    const set: Set<number> = new Set();
    const r: number = ~~(Math.sqrt(num) + 1);
    for (let i = 1; i < r; i++) {
      if (num % i === 0) {
        set.add(i);
        set.add(num / i);
      }
    }
    return set;
  }
};

while TypeScript's inference couldn't give us a type-checkable answer:

7:28 - error TS2365: Operator '+=' cannot be applied to types 'number' and 'unknown'.

7       for (const i of set) res += i;
                             ~~~~~~~~
License
Apache-2.0
Crate facts
Not measured
  • Runs on, Native code and Python bindings read crates on crates.io. This entry lists no crate.

Compare with similar projects

Order: strongest tier first, then most downloads, then name, with unmeasured values last. A library counts downloads over 30 days. An app counts binary downloads per release. An app with none counts its crate downloads over 30 days. ML Rust runs this catalogue, so its closest project always gets a row.

opentau beside the projects that share a topic with it
ProjectDownloadsDependentsStarsLatest releaseRuns on (GPU)Native codeLicense
opentau This page App Listed Dormant

Using Large Language Models for Repo-wide Type Prediction

Not measured Not applicable 121 Not measured Not measured Not measured Apache-2.0
tabby App Featured

Self-hosted AI coding assistant

8,774 binary downloads per release Not applicable 33,925 Not measured Not measured Not measured
llmfit App Featured

Right-size LLM models to your system hardware. Interactive TUI and CLI to match models against available RAM, CPU, and GPU.

3,821 binary downloads per release Not applicable 38,787 1.1.20 Metal No native code found MIT
agent App Listed

Ship your code, on autopilot. An open source agent that lives on your machines 24/7 and keeps your apps running. 🦀

19,860 binary downloads per release Not applicable 1,859 0.3.88 GPU support unknown Binds native code: tree-sitter, tree-sitter-language, tikv-jemalloc-sys (feature jemalloc) Apache-2.0
lsp-ai App Listed Dormant

LSP-AI is an open-source language server that serves as a backend for AI-powered functionality, designed to assist and empower software engineers, not replace them.

2,314 binary downloads per release Not applicable 3,210 0.7.0 CUDA, Metal Binds native code: tree-sitter, tree-sitter-language, llama-cpp-sys-2 (feature cuda). Build uses a native toolchain: cc MIT

OpenTelemetry components and utilities for Langfuse LLM observability

2,119 downloads over 30 days 2 4 0.7.2 GPU support unknown No native code found Apache-2.0 OR MIT
blazr App Listed

Inference server for LLMs, speech and embeddings: OpenAI-compatible routes with streaming, plus speech synthesis (TTS) and transcription (ASR). Serves Llama, Mistral, Qwen, Mamba2, MLA+MoE and hybrid models from SafeTensors, AWQ, GPTQ and GGUF.

8 binary downloads per release Not applicable 7 0.7.1 CUDA, WebGPU Binds native code: ring (feature distributed) Apache-2.0
Why ListedNo archive, New or Featured rule places it, so it is Listed.

What it lacks for Featured

What else the check found

  • The last activity is on 10 Dec 2023, 1065 days ago. That reaches the 365 days the rules expect, so the project is dormant. Dormancy rule

Not measured: Latest release, Unpatched vulnerabilities past the grace window, Binary downloads per release, First release.

Exact rules: Listed, Featured app. The check on applied thresholds version 9.

Every rule outcome, as the check records it
  • expected-interval: app: E = 365 days
  • dormancy: last activity 2023-12-10, 1065 days ago, E = 365: dormant
  • featured: not featured: dormant and not stable
  • listed: listed: no archive, new or featured rule applies

Measurements and sources

Each signal row shows the raw value, its source and the fetch date. The Sources list gathers every distinct source on this page. Movement needs more history.

Sources4 sources
AdoptionNot measured
Adoption measurements
SignalValueSourceFetchedCounts
Active dependentsNot measuredYes
New dependents (365 days)Not measuredYes
Peak active dependentsNot measuredYes
Active dependents as a share of the peakNot measuredYes
Binary downloads per releaseNot measuredYes
MaintenanceLatest human commit: 2 years ago8 not measured
Maintenance measurements
SignalValueSourceFetchedCounts
Latest human commit2023-12-10GitHub repositoryYes
Latest releaseNot measuredYes
First releaseNot measuredYes
Latest versionNot measuredYes
Days the oldest outside issue waits for a reply0GitHub repositoryYes
Maintainer replies over 365 days0GitHub repositoryYes
Repository archivedfalseGitHub repositoryYes
Repository gonefalseGitHub repositoryYes
Release commit or manifest in the repoNot measuredYes
Published docsNot measuredYes
Latest release fails cargo checkNot measuredYes
Build check dateNot measuredYes
Build check toolchainNot measuredYes
SecurityNot measured
Security measurements
SignalValueSourceFetchedCounts
Unpatched vulnerabilities past the grace windowNot measuredYes
RustSec unmaintained advisoryNot measuredYes
Deprecation noticeNot measuredYes
Deprecated requirementNot measuredYes
Distribution0 binary downloads over the last 3 releases10 not measured
Distribution measurements
SignalValueSourceFetchedCounts
ReleasesNot measuredYes
Downloads over 30 daysNot measuredYes
Binary downloads over the last 3 releases0GitHub releasesYes
Homebrew installs on request over 365 daysNot measuredYes
Repology repository familiesNot measuredYes
Docker pull growth over 90 daysNot measuredYes
Docker pulls at the growth baselineNot measuredYes
Every version a prereleaseNot measuredYes
Every version 0.0.xNot measuredYes
Every version yankedNot measuredYes
Crate deleted from crates.ioNot measuredYes
Repository121 stars, 7 forks, 2 contributors, 513 commits. Stars alone never qualify a project.1 not measured
Repository measurements
SignalValueSourceFetchedCounts
GitHub stars121GitHub repositoryYes — one part of the repository route
GitHub forks7GitHub repositoryYes — one part of the repository route
Contributors with a GitHub account2GitHub contributorsYes — one part of the repository route
Commits on the default branch513GitHub repositoryNo
Repository's main crate left out of the entryNot measuredYes
Facts that never count
Facts that never count measurements
SignalValueSourceFetchedCounts
LicenseApache-2.0GitHub repositoryNo
Runs on, from listed crates and their direct dependenciesNot measuredNo
What shows each backendNot measuredNo
Native code, from listed crates and their dependenciesNot measuredNo
Python bindings, from direct dependencies of listed cratesNot measuredNo
Crates that depend on a listed crate directlyNot measuredNo

Corrections and review history

Report a correction

No correction applies to this entry.

Judgments

A judgment enters only through an approved issue and a merged pull request.

No judgment applies to this entry.